之前找ai改了好久的dataviewjs能运行代码,有用到dv.view:
原版:【DV脚本】在单篇笔记里实现类 Notion Database 的轻量数据库、【文章汇总】Moy的OB分享(兼答疑贴) - #10,来自 Moy
渲染结果+笔记中代码

```dataviewjs
dv.view("页内数据库・可分组", {
showImage: true, //图片列开关
showLink: false, //链接列开关
groupBy: "分类", //分组依据
hideGroupByColumn: true, // 隐藏用于分组的列
filterBy: [
{ column: "分类", value: "管" },
{ column: "价格", value: "米" }
]
})
```
笔记中内容:
##### 4分管
‹ 参数::内径近15mm;外径21.25› ‹价格::› ‹分类::水管› ‹备注/评价::1›
##### 6分管
‹ 参数::内径20mm,外径26.5› ‹价格::› ‹分类::水管› ‹备注/评价::2›
##### 1寸
‹ 参数::内径25mm› ‹价格::› ‹分类::水管› ‹备注/评价::›
##### 2分
‹ 参数::内径8;外径12› ‹价格::› ‹分类::水管› ‹备注/评价::›
##### 3分
‹ 参数::内径10;外径16› ‹价格::› ‹分类::水管› ‹备注/评价::›
##### 20管
‹ 参数::外径20mm› ‹价格::2/米› ‹分类::线管› ‹备注/评价::›
##### 16管
‹ 参数::› ‹价格::1.3/米› ‹分类::线管› ‹备注/评价::›
##### 25管
‹ 参数::› ‹价格::4/米› ‹分类::线管› ‹备注/评价::›
##### 32管
‹ 参数::› ‹价格::› ‹分类::线管› ‹备注/评价::›
##### 20管
‹ 参数::› ‹价格::2.2/米› ‹分类::PVC管› ‹备注/评价::›
##### 40管
‹ 参数::› ‹价格::5/米› ‹分类::PVC管› ‹备注/评价::›
##### 50管
‹ 参数::› ‹价格::8/米› ‹分类::PVC管› ‹备注/评价::›
##### 63管
‹ 参数::› ‹价格::13/米› ‹分类::PVC管› ‹备注/评价::›
##### 75管
‹ 参数::› ‹价格::19/米› ‹分类::PVC管› ‹备注/评价::›
##### 110管
‹ 参数::› ‹价格::31/米› ‹分类::PVC管› ‹备注/评价::›
##### 1216管(4分)
‹ 参数::› ‹价格::1.6/米› ‹分类::铝塑管› ‹备注/评价::›
##### 1620管(6分)
‹ 参数::› ‹价格::2.3/米› ‹分类::铝塑管› ‹备注/评价::›
https://forum-zh.obsidian.md/t/topic/49233/10
##### 25/1寸
‹ 参数::› ‹价格::4.9/米› ‹分类::铝塑管› ‹备注/评价::›
[【DV脚本】在单篇笔记里实现类 Notion Database 的轻量数据库](https://forum-zh.obsidian.md/t/topic/38026)
##### 32/1.2寸
‹ 参数::› ‹价格::7.9/米› ‹分类::铝塑管› ‹备注/评价::›
库中单独的"页内数据库・可分组(标签页展示版).js"文件内装的代码
// ===================================================================
// 1. 动态注入 CSS 样式 (无需单独的 CSS 代码片段)
// ===================================================================
const cssId = 'dataview-js-tabs-styles';
if (!document.getElementById(cssId)) {
const styleEl = document.createElement('style');
styleEl.id = cssId;
styleEl.innerHTML = `
/* Dataview JS Tabs Container */
.dataview-js-tabs {
padding: 0;
}
/* Tab Buttons */
.dataview-tab-buttons {
margin-bottom: 1em;
border-bottom: 1px solid var(--background-modifier-border);
}
.dataview-tab-button {
background-color: transparent;
border: none;
padding: 8px 12px;
cursor: pointer;
font-size: var(--font-ui-medium);
color: var(--text-muted);
border-bottom: 2px solid transparent;
margin-right: 8px;
transition: all 0.2s ease-in-out;
}
.dataview-tab-button:hover {
color: var(--text-normal);
background-color: var(--background-modifier-hover);
}
.dataview-tab-button.active {
color: var(--interactive-accent);
border-bottom-color: var(--interactive-accent);
font-weight: var(--font-semibold);
}
`;
document.head.appendChild(styleEl);
}
// ===================================================================
// 2. JavaScript 逻辑
// ===================================================================
const useList = false;
const curNote = dv.current();
// --- 用户配置选项 ---
const groupBy = input?.groupBy;
const hideGroupByColumn = input?.hideGroupByColumn ?? false;
const filterBy = input?.filterBy;
const showLink = input?.showLink ?? true;
const showImage = input?.showImage ?? true;
if (!curNote){
dv.span("当前文档未加载,请重新打开");
return;
}
// ... (数据解析和筛选逻辑与之前完全相同) ...
let tarFile = await app.vault.getAbstractFileByPath(curNote.file.path);
const curFileMeta = app.metadataCache.getFileCache(tarFile);
const headings = curFileMeta.headings;
const headingsList = headings.map( k => k.heading )
const curFilePath = curNote.file.path;
const curTFile = await app.vault.getFileByPath(curFilePath)
const content = await app.vault.cachedRead(curTFile);
if (!headings) {
dv.span("当前文档缺少标题");
return;
}
const meta = content.matchAll(/#+ (.*)\n+(‹.*\:.*›[ \t]*)(?:\n+(\[.*\]\(.*\)|https?:\/\/[^\s]+)[ \t]*)?(?:\n+(\!\[\[.*\]\][ \t]*))?/gm);
let tableHeads = [];
if (showImage) tableHeads.push("图片");
tableHeads.push("标题");
if (showLink) tableHeads.push("链接");
let metaValues = [];
if (meta) {
for (let m of meta) {
let title = m[1].trim();
if (headingsList.indexOf(title) == -1) continue;
let metaList = m[2].trim().split(" ");
let metaDict = {};
if (showImage) {
let image = m[4] ?? "";
if (image) {
let imageLinkMatch = image.match(/\!\[\[(.*?)\]\]/);
if (imageLinkMatch && imageLinkMatch[1]) {
const imageName = imageLinkMatch[1];
const imageFile = app.metadataCache.getFirstLinkpathDest(imageName, curNote.file.path);
if (imageFile) {
const resPath = app.vault.adapter.getResourcePath(imageFile.path);
metaDict["图片"] = `<img src="${resPath}" style="max-width: 50px;" />`;
} else {
metaDict["图片"] = `<span style="color: var(--text-muted); font-style: italic;">图片未找到: ${imageName}</span>`;
}
}
}
}
for (let i = 0; i < metaList.length; i++) {
let keyValue = metaList[i].replace("‹", "").replace("›", "").split("::");
let key = keyValue[0].trim();
if (tableHeads.indexOf(key) == -1) tableHeads.push(key);
if (useList) {
let value = [keyValue[1]];
metaDict[key] = metaDict[key] ? metaDict[key].concat(value) : value;
} else {
let value = keyValue[1].trim();
value = value.replace(/;/g, ',');
if (value.includes(',')) {
metaDict[key] = value.split(',').map(v => v.trim());
} else {
metaDict[key] = metaDict[key] ? metaDict[key] + `,${value}` : value;
}
}
}
if (showLink) {
let url = m[3] ?? "";
if (url) {
if (url.startsWith('http')) {
url = `[🔗](${url})`;
} else {
url = url.replace(/\[(.*?)\]/, "[🔗]");
}
}
metaDict["链接"] = url;
}
const titleIndex = showImage ? 1 : 0;
let returnValuesList = [];
for (let i = 0; i < tableHeads.length; i++) {
const currentHead = tableHeads[i];
if (i === titleIndex) {
returnValuesList.push(m[1].trim());
} else {
returnValuesList.push(metaDict[currentHead] ? metaDict[currentHead] : "");
}
}
metaValues.push(returnValuesList);
}
}
if (filterBy && Array.isArray(filterBy) && filterBy.length > 0) {
const filteredValues = metaValues.filter(row => {
return filterBy.every(condition => {
const columnIndex = tableHeads.indexOf(condition.column);
if (columnIndex === -1) return false;
const cellValue = row[columnIndex];
const filterValue = String(condition.value).toLowerCase();
if (Array.isArray(cellValue)) {
return cellValue.some(item => String(item).toLowerCase().includes(filterValue));
} else if (cellValue) {
return String(cellValue).toLowerCase().includes(filterValue);
}
return false;
});
});
metaValues = filteredValues;
}
// --- 渲染与转换逻辑(支持列表显示版) ---
if (metaValues.length === 0) {
dv.span("🔍 根据您的条件,没有找到任何匹配项。");
} else if (groupBy && tableHeads.includes(groupBy)) {
const mainContainer = dv.el('div', '', { cls: 'dataview-js-tabs' });
const buttonContainer = mainContainer.createEl('div', { cls: 'dataview-tab-buttons' });
const groupedData = new Map();
const groupByIndex = tableHeads.indexOf(groupBy);
metaValues.forEach(row => {
const groupKeySource = row[groupByIndex];
const keys = Array.isArray(groupKeySource) ? groupKeySource : [groupKeySource];
keys.forEach(key => {
if (!groupedData.has(key)) {
groupedData.set(key, []);
}
groupedData.get(key).push(row);
});
});
let originalTitleIndex = 0;
if (showImage) originalTitleIndex = 1;
const contentEls = [];
const buttons = [];
for (const [key, rows] of groupedData) {
const contentEl = mainContainer.createEl('div', { cls: 'tab-content' });
contentEls.push(contentEl);
let titleIndexForRender = originalTitleIndex;
let renderHeads = [...tableHeads];
if (hideGroupByColumn) {
const idx = renderHeads.indexOf(groupBy);
if (idx > -1) {
renderHeads.splice(idx, 1);
if (idx < titleIndexForRender) titleIndexForRender--;
}
}
let localSerialNumber = 1;
const rowsWithLocalSerial = rows.map(row => {
let newRow = [...row];
if (hideGroupByColumn) {
newRow.splice(groupByIndex, 1);
}
const titleText = newRow[titleIndexForRender] || "未命名标题";
const safeAnchor = titleText.replace(/[\s\[\]\/\\#|]+/g, '-');
const titleWithSerial = `<span style="color: var(--text-muted); font-size: 0.8em;">${localSerialNumber}.</span> [[#${safeAnchor}]]`;
newRow[titleIndexForRender] = titleWithSerial;
localSerialNumber++;
return newRow;
});
const tableEl = contentEl.createEl('table', { cls: 'table-view-table' });
const theadEl = tableEl.createEl('thead');
const headerRowEl = theadEl.createEl('tr');
renderHeads.forEach(header => headerRowEl.createEl('th', { text: header }));
const tbodyEl = tableEl.createEl('tbody');
rowsWithLocalSerial.forEach(row => {
const rowEl = tbodyEl.createEl('tr');
row.forEach(cell => {
const cellEl = rowEl.createEl('td');
// 【关键改动】将数组用换行符连接,实现列表显示
const cellContent = Array.isArray(cell) ? cell.join('<br>') : String(cell);
const match = cellContent.match(/<span[^>]*>.*?<\/span>\s*\[\[#(.+?)\]\]/);
if (match) {
const anchor = match[1];
const linkText = anchor.replace(/-/g, ' ');
const spanEl = cellEl.createEl('span');
spanEl.innerHTML = cellContent.substring(0, match.index + match[0].length).replace(`[[#${anchor}]]`, '');
const linkEl = cellEl.createEl('a', {
text: linkText,
cls: 'internal-link',
href: `#${anchor}`
});
linkEl.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
app.workspace.openLinkText(`#${anchor}`, curNote.file.path, false);
});
} else {
let htmlContent = cellContent.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
cellEl.innerHTML = htmlContent;
}
});
});
const button = buttonContainer.createEl('button', { text: key, cls: 'dataview-tab-button' });
buttons.push(button);
}
if (contentEls.length > 0) {
contentEls.forEach(el => el.style.display = 'none');
contentEls[0].style.display = 'block';
buttons.forEach(btn => btn.classList.remove('active'));
buttons[0].classList.add('active');
}
buttons.forEach((button, index) => {
button.addEventListener('click', () => {
contentEls.forEach(el => el.style.display = 'none');
buttons.forEach(btn => btn.classList.remove('active'));
contentEls[index].style.display = 'block';
button.classList.add('active');
});
});
} else {
if (groupBy) {
dv.span(`⚠️ 未找到名为 "${groupBy}" 的列,将显示所有结果。`);
}
let originalTitleIndex = 0;
if (showImage) originalTitleIndex = 1;
let globalSerialNumber = 1;
const rowsWithSerial = metaValues.map(row => {
const newRow = [...row];
const titleText = newRow[originalTitleIndex] || "未命名标题";
const safeAnchor = titleText.replace(/[\s\[\]\/\\#|]+/g, '-');
const titleWithSerial = `<span style="color: var(--text-muted); font-size: 0.8em;">${globalSerialNumber}.</span> [[#${safeAnchor}]]`;
newRow[originalTitleIndex] = titleWithSerial;
globalSerialNumber++;
return newRow;
});
dv.table(tableHeads, rowsWithSerial);
}