first commit

This commit is contained in:
douboer
2026-03-21 18:57:10 +08:00
commit c49aa1a5e9
570 changed files with 107167 additions and 0 deletions

View File

@@ -0,0 +1,868 @@
/* global Page, wx, require, clearTimeout, setTimeout */
const {
addRecord,
searchRecords,
removeRecord,
updateRecord,
getSettings,
DEFAULT_VOICE_RECORD_CATEGORY_FALLBACK,
normalizeVoiceRecordCategories
} = require("../../utils/storage");
const { pageOf } = require("../../utils/pagination");
const { buildThemeStyle, applyNavigationBarTheme } = require("../../utils/themeStyle");
const { buildButtonIconThemeMaps } = require("../../utils/themedIcons");
const { getWindowMetrics } = require("../../utils/systemInfoCompat");
const { buildPageCopy, formatTemplate, normalizeUiLanguage, t } = require("../../utils/i18n");
const { buildSvgButtonPressData, createSvgButtonPressMethods } = require("../../utils/svgButtonFeedback");
const PAGE_SIZE = 15;
/**
* 左滑动作区固定为 2x2 四键:左列“废弃 / 已处理”,右列“复制 / 删除”。
* 这里提供一个总宽度兜底,实际交互仍优先读取真实节点宽度。
*/
const SWIPE_ACTION_WIDTH_RPX = 240;
const DEFAULT_WINDOW_WIDTH_PX = 375;
const SWIPE_AXIS_LOCK_THRESHOLD_PX = 6;
const RECORD_EDIT_AUTOSAVE_DELAY_MS = 400;
const QUICK_CATEGORY_DIALOG_MIN_WIDTH_PX = 120;
const QUICK_CATEGORY_DIALOG_MAX_WIDTH_PX = 240;
const QUICK_CATEGORY_DIALOG_MIN_HEIGHT_PX = Math.round((QUICK_CATEGORY_DIALOG_MIN_WIDTH_PX * 3) / 4);
const QUICK_CATEGORY_BUBBLE_GAP_PX = 8;
const QUICK_CATEGORY_BUBBLE_RADIUS_BASE_PX = 12;
const QUICK_CATEGORY_BUBBLE_RADIUS_STEP_PX = 7.6;
const CATEGORY_COLOR_PALETTE = [
{ background: "rgba(91, 210, 255, 0.18)", border: "rgba(91, 210, 255, 0.36)" },
{ background: "rgba(255, 143, 107, 0.18)", border: "rgba(255, 143, 107, 0.36)" },
{ background: "rgba(141, 216, 123, 0.18)", border: "rgba(141, 216, 123, 0.36)" },
{ background: "rgba(255, 191, 105, 0.18)", border: "rgba(255, 191, 105, 0.36)" },
{ background: "rgba(199, 146, 234, 0.18)", border: "rgba(199, 146, 234, 0.36)" },
{ background: "rgba(255, 111, 145, 0.18)", border: "rgba(255, 111, 145, 0.36)" },
{ background: "rgba(99, 210, 255, 0.18)", border: "rgba(99, 210, 255, 0.36)" },
{ background: "rgba(78, 205, 196, 0.18)", border: "rgba(78, 205, 196, 0.36)" },
{ background: "rgba(255, 209, 102, 0.18)", border: "rgba(255, 209, 102, 0.36)" },
{ background: "rgba(144, 190, 109, 0.18)", border: "rgba(144, 190, 109, 0.36)" }
];
function resolveRecordCategories(settings) {
const source = settings && typeof settings === "object" ? settings : {};
const categories = normalizeVoiceRecordCategories(source.voiceRecordCategories);
return categories.length > 0 ? categories : [DEFAULT_VOICE_RECORD_CATEGORY_FALLBACK];
}
function resolveVisibleCategory(rawCategory, categories) {
const normalized = String(rawCategory || "").trim();
if (normalized && categories.includes(normalized)) return normalized;
return DEFAULT_VOICE_RECORD_CATEGORY_FALLBACK;
}
function resolveDefaultCategory(settings, categories) {
const source = settings && typeof settings === "object" ? settings : {};
const options = Array.isArray(categories) && categories.length > 0 ? categories : resolveRecordCategories(source);
const normalized = String(source.voiceRecordDefaultCategory || "").trim();
if (normalized && options.includes(normalized)) return normalized;
return options[0] || DEFAULT_VOICE_RECORD_CATEGORY_FALLBACK;
}
/**
* 分类色按当前分类顺序稳定分配,优先保证当前页面里的分类彼此不同。
* 历史脏值已在外层先回退到可见分类,这里直接按展示分类映射即可。
*/
function buildCategoryStyle(category, orderedCategories) {
const label =
String(category || DEFAULT_VOICE_RECORD_CATEGORY_FALLBACK).trim() ||
DEFAULT_VOICE_RECORD_CATEGORY_FALLBACK;
const categories = Array.isArray(orderedCategories) ? orderedCategories : [];
const paletteIndex = Math.max(0, categories.indexOf(label));
const palette =
CATEGORY_COLOR_PALETTE[paletteIndex % CATEGORY_COLOR_PALETTE.length] || CATEGORY_COLOR_PALETTE[0];
return `background:${palette.background};border-color:${palette.border};`;
}
function clampNumber(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function resolveQuickCategoryBubbleSize(category) {
const length = Array.from(String(category || DEFAULT_VOICE_RECORD_CATEGORY_FALLBACK)).length;
return Math.min(52, Math.max(34, 24 + length * 7));
}
function intersectsPlacedBubble(left, top, size, placedBubbles) {
const centerX = left + size / 2;
const centerY = top + size / 2;
return placedBubbles.some((bubble) => {
const otherCenterX = bubble.left + bubble.size / 2;
const otherCenterY = bubble.top + bubble.size / 2;
const minDistance = size / 2 + bubble.size / 2 + QUICK_CATEGORY_BUBBLE_GAP_PX;
return Math.hypot(centerX - otherCenterX, centerY - otherCenterY) < minDistance;
});
}
function buildBubbleCloudForBox(orderedCategories, currentCategory, width, height, colorCategories) {
const centerX = width / 2;
const centerY = height / 2;
const placedBubbles = [];
let collisionCount = 0;
const items = orderedCategories.map((category, index) => {
const size = resolveQuickCategoryBubbleSize(category);
if (index === 0) {
const left = Math.max(0, Math.min(width - size, centerX - size / 2));
const top = Math.max(0, Math.min(height - size, centerY - size / 2));
placedBubbles.push({ left, top, size });
return {
active: category === currentCategory,
category,
style: `width:${size}px;height:${size}px;left:${left}px;top:${top}px;`,
categoryStyle: buildCategoryStyle(category, colorCategories)
};
}
let fallbackLeft = Math.max(0, Math.min(width - size, centerX - size / 2));
let fallbackTop = Math.max(0, Math.min(height - size, centerY - size / 2));
let placed = false;
for (let step = 1; step <= 320; step += 1) {
const angle = step * 0.72;
const radius =
QUICK_CATEGORY_BUBBLE_RADIUS_BASE_PX + Math.sqrt(step) * QUICK_CATEGORY_BUBBLE_RADIUS_STEP_PX;
const candidateLeft = centerX + Math.cos(angle) * radius - size / 2;
const candidateTop = centerY + Math.sin(angle) * radius * 1.08 - size / 2;
const isWithinWidth = candidateLeft >= 0 && candidateLeft + size <= width;
const isWithinHeight = candidateTop >= 0 && candidateTop + size <= height;
if (!isWithinWidth || !isWithinHeight) continue;
if (intersectsPlacedBubble(candidateLeft, candidateTop, size, placedBubbles)) continue;
fallbackLeft = candidateLeft;
fallbackTop = candidateTop;
placed = true;
break;
}
if (!placed) collisionCount += 1;
placedBubbles.push({ left: fallbackLeft, top: fallbackTop, size });
return {
active: category === currentCategory,
category,
style: `width:${size}px;height:${size}px;left:${fallbackLeft}px;top:${fallbackTop}px;`,
categoryStyle: buildCategoryStyle(category, colorCategories)
};
});
return { collisionCount, items };
}
function buildQuickCategoryLayout(categories, currentCategory, maxWidthPx) {
const colorCategories = categories.slice();
const orderedCategories = categories.slice().sort((left, right) => {
if (left === currentCategory) return -1;
if (right === currentCategory) return 1;
return categories.indexOf(left) - categories.indexOf(right);
});
const longestLength = orderedCategories.reduce(
(max, category) => Math.max(max, Array.from(String(category || "")).length),
0
);
const estimatedWidth =
QUICK_CATEGORY_DIALOG_MIN_WIDTH_PX +
Math.max(0, orderedCategories.length - 3) * 22 +
Math.max(0, longestLength - 2) * 8;
const maxWidth = clampNumber(
maxWidthPx || QUICK_CATEGORY_DIALOG_MAX_WIDTH_PX,
QUICK_CATEGORY_DIALOG_MIN_WIDTH_PX,
QUICK_CATEGORY_DIALOG_MAX_WIDTH_PX
);
let bestWidth = clampNumber(estimatedWidth, QUICK_CATEGORY_DIALOG_MIN_WIDTH_PX, maxWidth);
let bestHeight = Math.max(QUICK_CATEGORY_DIALOG_MIN_HEIGHT_PX, Math.round((bestWidth * 3) / 4));
let bestLayout = buildBubbleCloudForBox(
orderedCategories,
currentCategory,
bestWidth,
bestHeight,
colorCategories
);
for (let width = QUICK_CATEGORY_DIALOG_MIN_WIDTH_PX; width <= maxWidth; width += 20) {
const height = Math.max(QUICK_CATEGORY_DIALOG_MIN_HEIGHT_PX, Math.round((width * 3) / 4));
const candidateLayout = buildBubbleCloudForBox(
orderedCategories,
currentCategory,
width,
height,
colorCategories
);
if (candidateLayout.collisionCount < bestLayout.collisionCount) {
bestWidth = width;
bestHeight = height;
bestLayout = candidateLayout;
}
if (candidateLayout.collisionCount === 0 && width >= bestWidth) {
bestWidth = width;
bestHeight = height;
bestLayout = candidateLayout;
break;
}
}
return {
width: bestWidth,
height: bestHeight,
items: bestLayout.items
};
}
/**
* 闪念页(小程序对齐 Web v2.2.0
* 1. 顶部提供搜索和分类过滤;
* 2. 支持左滑废弃 / 已处理 / 复制 / 删除、点击正文编辑、点击分类快速改分类;
* 3. 过滤后分页,底部分页与新增 / 导出分区展示。
*/
Page({
data: {
...buildSvgButtonPressData(),
themeStyle: "",
icons: {},
accentIcons: {},
copy: buildPageCopy("zh-Hans", "records"),
page: 1,
total: 0,
totalPages: 1,
rows: [],
query: "",
selectedCategory: "",
categoryOptions: [],
categoryMenuVisible: false,
quickCategoryPopupVisible: false,
quickCategoryRecordId: "",
quickCategoryItems: [],
quickCategoryWidthPx: QUICK_CATEGORY_DIALOG_MIN_WIDTH_PX,
quickCategoryHeightPx: QUICK_CATEGORY_DIALOG_MIN_HEIGHT_PX,
quickCategoryPanelStyle: "",
editPopupVisible: false,
editRecordId: "",
editContent: "",
editCategory: "",
editUpdatedAtText: "",
editUpdatedAtLabel: "",
pageIndicatorText: t("zh-Hans", "common.pageIndicator", { page: 1, total: 1 })
},
swipeRuntime: null,
editAutoSaveTimer: null,
onShow() {
this.syncWindowMetrics();
const settings = getSettings();
const language = normalizeUiLanguage(settings.uiLanguage);
const copy = buildPageCopy(language, "records");
const { icons, accentIcons } = buildButtonIconThemeMaps(settings);
applyNavigationBarTheme(settings);
wx.setNavigationBarTitle({ title: copy.navTitle || "闪念" });
this.setData({
themeStyle: buildThemeStyle(settings),
icons,
accentIcons,
copy
});
this.reload();
},
onHide() {
this.flushEditAutoSave();
},
onUnload() {
this.flushEditAutoSave();
},
syncWindowMetrics() {
try {
const info = getWindowMetrics(wx);
const width = Number(info.windowWidth);
const height = Number(info.windowHeight);
this.windowWidthPx = Number.isFinite(width) && width > 0 ? width : DEFAULT_WINDOW_WIDTH_PX;
this.windowHeightPx = Number.isFinite(height) && height > 0 ? height : 667;
} catch {
this.windowWidthPx = DEFAULT_WINDOW_WIDTH_PX;
this.windowHeightPx = 667;
}
},
buildQuickCategoryPanelStyle(anchorRect, layout) {
const rect = anchorRect && typeof anchorRect === "object" ? anchorRect : {};
const width = Number(layout && layout.width) || QUICK_CATEGORY_DIALOG_MIN_WIDTH_PX;
const height = Number(layout && layout.height) || QUICK_CATEGORY_DIALOG_MIN_HEIGHT_PX;
const windowWidth = Number(this.windowWidthPx) || DEFAULT_WINDOW_WIDTH_PX;
const windowHeight = Number(this.windowHeightPx) || 667;
const panelPadding = 12;
const gap = 8;
const buttonLeft = Number(rect.left) || 0;
const buttonTop = Number(rect.top) || 0;
const buttonWidth = Number(rect.width) || 0;
const buttonHeight = Number(rect.height) || 0;
let left = buttonLeft + buttonWidth + gap;
if (left + width + panelPadding > windowWidth) {
left = buttonLeft - width - gap;
}
left = clampNumber(left, panelPadding, Math.max(panelPadding, windowWidth - width - panelPadding));
const buttonCenterY = buttonTop + buttonHeight / 2;
let top = buttonCenterY - height / 2 - panelPadding;
top = clampNumber(top, panelPadding, Math.max(panelPadding, windowHeight - height - panelPadding));
return `left:${left}px;top:${top}px;`;
},
getSwipeRevealPx() {
if (Number.isFinite(this.swipeRevealPx) && this.swipeRevealPx > 0) {
return this.swipeRevealPx;
}
const windowWidth = Number(this.windowWidthPx) || DEFAULT_WINDOW_WIDTH_PX;
const actionWidthPx = Math.round((windowWidth * SWIPE_ACTION_WIDTH_RPX) / 750);
return Math.max(24, actionWidthPx);
},
syncSwipeRevealWidth() {
const query = this.createSelectorQuery();
query
.select(".record-item-actions-wrap")
.boundingClientRect((rect) => {
const width = Number(rect && rect.width);
if (!Number.isFinite(width) || width <= 0) return;
this.swipeRevealPx = Math.max(24, Math.round(width));
})
.exec();
},
/**
* 先按关键字过滤,再用当前有效分类做展示过滤,保持与 web 的语义一致。
*/
reload() {
const settings = getSettings();
const { icons, accentIcons } = buildButtonIconThemeMaps(settings);
const categoryOptions = resolveRecordCategories(settings);
const selectedCategory = categoryOptions.includes(this.data.selectedCategory)
? this.data.selectedCategory
: "";
const keyword = String(this.data.query || "").trim();
const filtered = searchRecords({ keyword }).filter(
(item) =>
!selectedCategory || resolveVisibleCategory(item.category, categoryOptions) === selectedCategory
);
const paged = pageOf(filtered, this.data.page, PAGE_SIZE);
const language = normalizeUiLanguage(settings.uiLanguage);
const nextOffsets = {};
const rows = paged.rows.map((item) => {
const displayCategory = resolveVisibleCategory(item.category, categoryOptions);
const swipeOffsetX =
this.swipeOffsets && Number(this.swipeOffsets[item.id]) ? this.swipeOffsets[item.id] : 0;
nextOffsets[item.id] = swipeOffsetX;
return {
...item,
processed: item.processed === true,
discarded: item.discarded === true,
isMuted: false,
displayCategory,
categoryStyle: buildCategoryStyle(displayCategory, categoryOptions),
timeText: this.formatTime(item.createdAt),
updatedAtText: this.formatTime(item.updatedAt),
contextLabelText: item.contextLabel || this.data.copy.contextFallback || "未设置上下文",
swipeOffsetX
};
});
this.swipeOffsets = nextOffsets;
this.swipeRuntime = null;
this.setData(
{
themeStyle: buildThemeStyle(settings),
icons,
accentIcons,
categoryOptions,
selectedCategory,
categoryMenuVisible: false,
page: paged.page,
total: paged.total,
totalPages: paged.totalPages,
rows,
pageIndicatorText: t(language, "common.pageIndicator", {
page: paged.page,
total: paged.totalPages
})
},
() => this.syncSwipeRevealWidth()
);
},
formatTime(input) {
if (!input) return "--";
const date = new Date(input);
if (Number.isNaN(+date)) return "--";
const pad = (value) => String(value).padStart(2, "0");
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
},
onQueryInput(event) {
this.setData({ query: String(event.detail.value || ""), page: 1 }, () => this.reload());
},
onToggleCategoryMenu() {
this.closeAllRows();
this.closeQuickCategoryPopup();
this.setData({ categoryMenuVisible: !this.data.categoryMenuVisible });
},
onSelectFilterCategory(event) {
const category = String(event.currentTarget.dataset.category || "");
this.setData({ selectedCategory: category, categoryMenuVisible: false, page: 1 }, () => this.reload());
},
onPrev() {
this.setData({ page: Math.max(1, this.data.page - 1) }, () => this.reload());
},
onNext() {
this.setData({ page: Math.min(this.data.totalPages, this.data.page + 1) }, () => this.reload());
},
/**
* 新增入口复用现有编辑弹层:
* 1. 先以空草稿和默认分类打开;
* 2. 首次输入非空内容后自动创建记录;
* 3. 后续继续沿用原有编辑自动保存链路。
*/
onOpenCreate() {
const settings = getSettings();
const categoryOptions = this.data.categoryOptions.length
? this.data.categoryOptions.slice()
: resolveRecordCategories(settings);
this.clearEditAutoSaveTimer();
this.closeAllRows();
this.closeQuickCategoryPopup();
this.setData({
categoryMenuVisible: false,
editPopupVisible: true,
editRecordId: "",
editContent: "",
editCategory: resolveDefaultCategory(settings, categoryOptions),
editUpdatedAtText: "",
editUpdatedAtLabel: this.data.copy.newRecordHint || "输入后自动保存为新闪念"
});
},
resolveTouchPoint(event) {
const point =
(event && event.touches && event.touches[0]) ||
(event && event.changedTouches && event.changedTouches[0]) ||
null;
if (!point) return null;
const x = Number(point.clientX);
const y = Number(point.clientY);
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
return { x, y };
},
clampSwipeOffset(value) {
const revealPx = this.getSwipeRevealPx();
const numeric = Number(value);
if (!Number.isFinite(numeric)) return 0;
if (numeric < -revealPx) return -revealPx;
if (numeric > 0) return 0;
return numeric;
},
findRowIndexById(id) {
return this.data.rows.findIndex((item) => item.id === id);
},
updateRowSwipeOffset(id, offset) {
const index = this.findRowIndexById(id);
if (index < 0) return;
const normalized = this.clampSwipeOffset(offset);
this.swipeOffsets[id] = normalized;
this.setData({ [`rows[${index}].swipeOffsetX`]: normalized });
},
closeOtherRows(exceptId) {
const updates = {};
this.data.rows.forEach((item, index) => {
if (item.id === exceptId) return;
if (item.swipeOffsetX === 0) return;
this.swipeOffsets[item.id] = 0;
updates[`rows[${index}].swipeOffsetX`] = 0;
});
if (Object.keys(updates).length > 0) this.setData(updates);
},
closeAllRows() {
const updates = {};
this.data.rows.forEach((item, index) => {
if (item.swipeOffsetX === 0) return;
this.swipeOffsets[item.id] = 0;
updates[`rows[${index}].swipeOffsetX`] = 0;
});
if (Object.keys(updates).length > 0) this.setData(updates);
},
onListTap() {
this.closeAllRows();
if (this.data.categoryMenuVisible) {
this.setData({ categoryMenuVisible: false });
}
this.closeQuickCategoryPopup();
},
onRecordTouchStart(event) {
const id = event.currentTarget.dataset.id;
if (!id) return;
const point = this.resolveTouchPoint(event);
if (!point) return;
this.closeOtherRows(id);
this.swipeRuntime = {
id,
startX: point.x,
startY: point.y,
startOffsetX: this.clampSwipeOffset((this.swipeOffsets && this.swipeOffsets[id]) || 0),
dragging: false,
blocked: false
};
},
onRecordTouchMove(event) {
const runtime = this.swipeRuntime;
if (!runtime || runtime.blocked) return;
const id = event.currentTarget.dataset.id;
if (!id || id !== runtime.id) return;
const point = this.resolveTouchPoint(event);
if (!point) return;
const deltaX = point.x - runtime.startX;
const deltaY = point.y - runtime.startY;
if (!runtime.dragging) {
if (
Math.abs(deltaX) < SWIPE_AXIS_LOCK_THRESHOLD_PX &&
Math.abs(deltaY) < SWIPE_AXIS_LOCK_THRESHOLD_PX
) {
return;
}
if (Math.abs(deltaY) > Math.abs(deltaX)) {
runtime.blocked = true;
return;
}
runtime.dragging = true;
}
this.updateRowSwipeOffset(id, runtime.startOffsetX + deltaX);
},
onRecordTouchEnd(event) {
const runtime = this.swipeRuntime;
this.swipeRuntime = null;
if (!runtime || runtime.blocked) return;
const id = event.currentTarget.dataset.id;
if (!id || id !== runtime.id) return;
const revealPx = this.getSwipeRevealPx();
const current = this.clampSwipeOffset((this.swipeOffsets && this.swipeOffsets[id]) || 0);
const shouldOpen = current <= -revealPx * 0.45;
this.updateRowSwipeOffset(id, shouldOpen ? -revealPx : 0);
},
onDelete(event) {
const id = String(event.currentTarget.dataset.id || "");
if (!id) return;
removeRecord(id);
if (this.swipeOffsets) delete this.swipeOffsets[id];
wx.showToast({ title: this.data.copy?.toast?.deleted || "已删除", icon: "success" });
this.reload();
},
onCopy(event) {
const id = String(event.currentTarget.dataset.id || "");
const record = this.data.rows.find((item) => item.id === id);
if (!record) return;
wx.setClipboardData({
data: String(record.content || ""),
success: () => {
this.closeAllRows();
wx.showToast({ title: this.data.copy?.toast?.copied || "已复制", icon: "success" });
}
});
},
/**
* “已处理”是一次性整理动作:
* 1. 仅把当前闪念标记为已处理;
* 2. 已处理状态继续允许编辑,但列表会整体降到 60% opacity
* 3. 重复点击保持幂等,只回显同一条成功提示。
*/
onMarkProcessed(event) {
const id = String(event.currentTarget.dataset.id || "");
const record = this.data.rows.find((item) => item.id === id);
if (!record) return;
if (record.processed && !record.discarded) {
this.closeAllRows();
wx.showToast({ title: this.data.copy?.toast?.processed || "已处理", icon: "success" });
return;
}
const updated = updateRecord({
id: record.id,
content: record.content,
category: record.category || record.displayCategory,
processed: true,
discarded: false
});
if (!updated) {
wx.showToast({ title: this.data.copy?.toast?.updateFailed || "更新失败", icon: "none" });
return;
}
this.closeAllRows();
wx.showToast({ title: this.data.copy?.toast?.processed || "已处理", icon: "success" });
this.reload();
},
/**
* “废弃”与“已处理”同属终态:
* 1. 写入废弃后会自动取消已处理,保证两种状态互斥;
* 2. 当前版本仅按需求收口按钮与状态,不额外改变正文内容;
* 3. 列表视觉同样按 60% opacity 弱化,便于后续统一筛看。
*/
onMarkDiscarded(event) {
const id = String(event.currentTarget.dataset.id || "");
const record = this.data.rows.find((item) => item.id === id);
if (!record) return;
if (record.discarded && !record.processed) {
this.closeAllRows();
wx.showToast({ title: this.data.copy?.toast?.discarded || "已废弃", icon: "success" });
return;
}
const updated = updateRecord({
id: record.id,
content: record.content,
category: record.category || record.displayCategory,
processed: false,
discarded: true
});
if (!updated) {
wx.showToast({ title: this.data.copy?.toast?.updateFailed || "更新失败", icon: "none" });
return;
}
this.closeAllRows();
wx.showToast({ title: this.data.copy?.toast?.discarded || "已废弃", icon: "success" });
this.reload();
},
/**
* 点击左侧分类胶囊,改为页内弹层气泡云,避免原生 action sheet 破坏当前页面语义。
*/
onQuickCategoryTap(event) {
const recordId = String(event.currentTarget.dataset.id || "");
const record = this.data.rows.find((item) => item.id === recordId);
if (!record) return;
const categories = this.data.categoryOptions.slice();
if (categories.length === 0) return;
const maxWidth = Math.max(
QUICK_CATEGORY_DIALOG_MIN_WIDTH_PX,
Math.min(
QUICK_CATEGORY_DIALOG_MAX_WIDTH_PX,
(Number(this.windowWidthPx) || DEFAULT_WINDOW_WIDTH_PX) - 48
)
);
const layout = buildQuickCategoryLayout(categories, record.displayCategory, maxWidth);
const anchorId = `#quick-category-${record.id}`;
const query = this.createSelectorQuery();
query.select(anchorId).boundingClientRect((rect) => {
const panelStyle = this.buildQuickCategoryPanelStyle(rect, layout);
this.closeAllRows();
this.setData({
quickCategoryPopupVisible: true,
quickCategoryRecordId: record.id,
quickCategoryItems: layout.items,
quickCategoryWidthPx: layout.width,
quickCategoryHeightPx: layout.height,
quickCategoryPanelStyle: panelStyle
});
});
query.exec();
},
closeQuickCategoryPopup() {
if (!this.data.quickCategoryPopupVisible) return;
this.setData({
quickCategoryPopupVisible: false,
quickCategoryRecordId: "",
quickCategoryItems: [],
quickCategoryPanelStyle: ""
});
},
onApplyQuickCategory(event) {
const recordId = String(this.data.quickCategoryRecordId || "");
const record = this.data.rows.find((item) => item.id === recordId);
const nextCategory = String(event.currentTarget.dataset.category || "");
if (!record || !nextCategory || nextCategory === record.displayCategory) {
this.closeQuickCategoryPopup();
return;
}
const updated = updateRecord({
id: record.id,
content: record.content,
category: nextCategory
});
if (!updated) {
wx.showToast({ title: this.data.copy?.toast?.updateFailed || "更新失败", icon: "none" });
return;
}
wx.showToast({ title: this.data.copy?.toast?.categoryUpdated || "分类已更新", icon: "success" });
this.closeQuickCategoryPopup();
this.reload();
},
onOpenEdit(event) {
const recordId = String(event.currentTarget.dataset.id || "");
const record = this.data.rows.find((item) => item.id === recordId);
if (!record) return;
if (Number(record.swipeOffsetX) < 0) {
this.updateRowSwipeOffset(recordId, 0);
return;
}
this.clearEditAutoSaveTimer();
this.setData({
editPopupVisible: true,
editRecordId: record.id,
editContent: record.content || "",
editCategory: record.displayCategory,
editUpdatedAtText: record.updatedAtText || this.formatTime(record.updatedAt),
editUpdatedAtLabel: formatTemplate(this.data.copy.updatedAtPrefix, {
time: record.updatedAtText || this.formatTime(record.updatedAt)
})
});
},
resetEditState() {
this.clearEditAutoSaveTimer();
this.setData({
editPopupVisible: false,
editRecordId: "",
editContent: "",
editCategory: "",
editUpdatedAtText: "",
editUpdatedAtLabel: ""
});
},
onCloseEdit() {
this.flushEditAutoSave();
this.resetEditState();
},
onEditContentInput(event) {
this.setData({ editContent: String(event.detail.value || "") }, () => this.scheduleEditAutoSave());
},
onEditCategoryTap(event) {
const category = String(event.currentTarget.dataset.category || "");
if (!category) return;
this.setData({ editCategory: category }, () => this.scheduleEditAutoSave());
},
/**
* 编辑弹窗改为自动保存:
* 1. 内容或分类变化后走 400ms 防抖;
* 2. 关闭弹窗、页面隐藏、页面卸载时立即 flush
* 3. 空内容继续不写回存储,避免误清空历史闪念。
*/
persistEditDraft() {
const recordId = String(this.data.editRecordId || "").trim();
const content = String(this.data.editContent || "").trim();
const category = String(this.data.editCategory || "").trim();
if (!content) return null;
if (!recordId) {
const created = addRecord(content, "", {
category: category || DEFAULT_VOICE_RECORD_CATEGORY_FALLBACK
});
if (!created) return null;
const updatedAtText = this.formatTime(created.updatedAt);
this.reload();
this.setData({
editRecordId: created.id,
editContent: created.content,
editCategory: created.category,
editUpdatedAtText: updatedAtText,
editUpdatedAtLabel: formatTemplate(this.data.copy.updatedAtPrefix, { time: updatedAtText })
});
return created;
}
const currentRecord = searchRecords({ keyword: "" }).find((item) => item.id === recordId);
const normalizedCategory = category || DEFAULT_VOICE_RECORD_CATEGORY_FALLBACK;
if (
currentRecord &&
String(currentRecord.content || "").trim() === content &&
String(currentRecord.category || DEFAULT_VOICE_RECORD_CATEGORY_FALLBACK).trim() === normalizedCategory
) {
return currentRecord;
}
const updated = updateRecord({
id: recordId,
content,
category: normalizedCategory
});
if (!updated) return null;
this.reload();
const updatedAtText = this.formatTime(updated.updatedAt);
this.setData({
editUpdatedAtText: updatedAtText,
editUpdatedAtLabel: formatTemplate(this.data.copy.updatedAtPrefix, { time: updatedAtText })
});
return updated;
},
clearEditAutoSaveTimer() {
if (!this.editAutoSaveTimer) return;
clearTimeout(this.editAutoSaveTimer);
this.editAutoSaveTimer = null;
},
scheduleEditAutoSave() {
if (!this.data.editPopupVisible) return;
this.clearEditAutoSaveTimer();
this.editAutoSaveTimer = setTimeout(() => {
this.editAutoSaveTimer = null;
this.persistEditDraft();
}, RECORD_EDIT_AUTOSAVE_DELAY_MS);
},
flushEditAutoSave() {
this.clearEditAutoSaveTimer();
this.persistEditDraft();
},
onExport() {
const settings = getSettings();
const categoryOptions = resolveRecordCategories(settings);
const selectedCategory = categoryOptions.includes(this.data.selectedCategory)
? this.data.selectedCategory
: "";
const keyword = String(this.data.query || "").trim();
const rows = searchRecords({ keyword }).filter(
(item) =>
!selectedCategory || resolveVisibleCategory(item.category, categoryOptions) === selectedCategory
);
const content = rows
.map((item) => {
const displayCategory = resolveVisibleCategory(item.category, categoryOptions);
const exportFields = this.data.copy.exportFields || {};
return [
`${exportFields.createdAt || "创建时间"}${this.formatTime(item.createdAt)}`,
`${exportFields.updatedAt || "更新时间"}${this.formatTime(item.updatedAt)}`,
`${exportFields.server || "服务器"}${item.serverId || "-"}`,
`${exportFields.category || "分类"}${displayCategory}`,
`${exportFields.context || "上下文"}${item.contextLabel || "-"}`,
String(item.content || "")
].join("\n");
})
.join("\n\n");
wx.setClipboardData({
data: content || "",
success: () => wx.showToast({ title: this.data.copy?.toast?.exported || "闪念已复制", icon: "success" })
});
},
noop() {},
...createSvgButtonPressMethods()
});

View File

@@ -0,0 +1,7 @@
{
"navigationBarTitleText": "闪念",
"disableScroll": true,
"usingComponents": {
"bottom-nav": "/components/bottom-nav/index"
}
}

View File

@@ -0,0 +1,194 @@
<view class="page-root records-page" style="{{themeStyle}}">
<view class="page-content">
<view class="surface-panel records-panel">
<view class="records-search-wrap">
<view class="records-search-shell">
<input
class="records-search-input"
type="text"
placeholder="{{copy.searchPlaceholder}}"
placeholder-class="records-search-input-placeholder"
value="{{query}}"
bindinput="onQueryInput"
/>
<button class="records-filter-btn" bindtap="onToggleCategoryMenu">
<text class="records-filter-arrow">{{categoryMenuVisible ? '▲' : '▼'}}</text>
</button>
</view>
<view wx:if="{{categoryMenuVisible}}" class="records-filter-menu">
<view
class="records-filter-option {{selectedCategory === '' ? 'active' : ''}}"
data-category=""
bindtap="onSelectFilterCategory"
>{{copy.allCategories}}</view
>
<view
wx:for="{{categoryOptions}}"
wx:key="*this"
class="records-filter-option {{selectedCategory === item ? 'active' : ''}}"
data-category="{{item}}"
bindtap="onSelectFilterCategory"
>{{item}}</view
>
</view>
</view>
<scroll-view class="surface-scroll records-list-scroll" scroll-y="true">
<view class="list-stack records-list" bindtap="onListTap">
<view
wx:for="{{rows}}"
wx:key="id"
class="record-item-shell {{item.isMuted ? 'record-item-shell-muted' : ''}}"
data-id="{{item.id}}"
bindtouchstart="onRecordTouchStart"
bindtouchmove="onRecordTouchMove"
bindtouchend="onRecordTouchEnd"
bindtouchcancel="onRecordTouchEnd"
>
<view class="record-item-actions-wrap {{(item.swipeOffsetX || 0) < 0 ? 'opened' : ''}}">
<button class="record-swipe-copy-btn" data-id="{{item.id}}" catchtap="onCopy">
<text class="record-swipe-btn-text">{{copy.swipeCopy}}</text>
</button>
<button class="record-swipe-processed-btn" data-id="{{item.id}}" catchtap="onMarkProcessed">
<text class="record-swipe-btn-text">{{copy.swipeProcessed}}</text>
</button>
<button class="record-swipe-discarded-btn" data-id="{{item.id}}" catchtap="onMarkDiscarded">
<text class="record-swipe-btn-text">{{copy.swipeDiscarded}}</text>
</button>
<button class="record-swipe-delete-btn" data-id="{{item.id}}" catchtap="onDelete">
<text class="record-swipe-btn-text">{{copy.swipeDelete}}</text>
</button>
</view>
<view class="record-item-track" style="transform: translateX({{item.swipeOffsetX || 0}}px);">
<view class="record-item-main">
<view class="record-item-category-hitbox" data-id="{{item.id}}" catchtap="onQuickCategoryTap">
<view
id="quick-category-{{item.id}}"
class="record-item-category"
style="{{item.categoryStyle}}"
>
<text class="record-item-category-text">{{item.displayCategory}}</text>
</view>
</view>
<view
class="card record-item {{item.processed ? 'record-item-processed' : ''}}"
data-id="{{item.id}}"
bindtap="onOpenEdit"
>
<view class="record-item-header">
<text class="record-item-time">{{item.timeText}}</text>
<text class="record-item-context">{{item.contextLabelText}}</text>
</view>
<text class="record-item-content {{item.discarded ? 'record-item-content-discarded' : ''}}">{{item.content || '--'}}</text>
</view>
</view>
</view>
</view>
<text wx:if="{{rows.length === 0}}" class="empty">{{copy.empty}}</text>
</view>
</scroll-view>
<view class="records-footer">
<view class="records-pagination">
<button class="btn" disabled="{{page <= 1}}" bindtap="onPrev">{{copy.prev}}</button>
<text class="records-pagination-text">{{pageIndicatorText}}</text>
<button class="btn" disabled="{{page >= totalPages}}" bindtap="onNext">{{copy.next}}</button>
</view>
<view class="records-footer-actions">
<button
class="btn records-footer-action-btn svg-press-btn"
hover-class="svg-press-btn-hover"
hover-start-time="0"
hover-stay-time="80"
data-press-key="records:add"
bindtouchstart="onSvgButtonTouchStart"
bindtouchend="onSvgButtonTouchEnd"
bindtouchcancel="onSvgButtonTouchEnd"
bindtap="onOpenCreate"
>
<image
class="records-footer-action-icon svg-press-icon"
src="{{pressedSvgButtonKey === 'records:add' ? (accentIcons.add || icons.add || '/assets/icons/add.svg') : (icons.add || '/assets/icons/add.svg')}}"
mode="aspectFit"
/>
<text>{{copy.addButton}}</text>
</button>
<button class="btn" bindtap="onExport">{{copy.exportButton}}</button>
</view>
</view>
</view>
</view>
<view wx:if="{{quickCategoryPopupVisible}}" class="records-quick-mask" bindtap="closeQuickCategoryPopup">
<view class="records-quick-panel" style="{{quickCategoryPanelStyle}}" catchtap="noop">
<view
class="records-quick-cloud"
style="width: {{quickCategoryWidthPx}}px; height: {{quickCategoryHeightPx}}px;"
>
<button
wx:for="{{quickCategoryItems}}"
wx:key="category"
class="records-quick-bubble {{item.active ? 'active' : ''}}"
style="{{item.style}} {{item.categoryStyle}}"
data-category="{{item.category}}"
bindtap="onApplyQuickCategory"
>
{{item.category}}
</button>
</view>
</view>
</view>
<view wx:if="{{editPopupVisible}}" class="records-edit-mask" bindtap="onCloseEdit">
<view class="records-edit-panel" catchtap="noop">
<scroll-view class="records-edit-category-scroll" scroll-x="true" show-scrollbar="false">
<view class="records-edit-category-row">
<view
wx:for="{{categoryOptions}}"
wx:key="*this"
class="records-edit-category-pill {{editCategory === item ? 'active' : ''}}"
data-category="{{item}}"
bindtap="onEditCategoryTap"
>{{item}}</view
>
</view>
</scroll-view>
<textarea
class="records-edit-textarea"
value="{{editContent}}"
maxlength="-1"
auto-height
placeholder="{{copy.editPlaceholder}}"
bindinput="onEditContentInput"
/>
<text class="records-edit-time">{{editUpdatedAtLabel}}</text>
<button
class="records-edit-close-btn svg-press-btn"
hover-class="svg-press-btn-hover"
hover-start-time="0"
hover-stay-time="80"
data-press-key="records:close-edit"
bindtouchstart="onSvgButtonTouchStart"
bindtouchend="onSvgButtonTouchEnd"
bindtouchcancel="onSvgButtonTouchEnd"
bindtap="onCloseEdit"
aria-label="{{copy.closeEditAriaLabel}}"
>
<image
class="records-edit-close-icon svg-press-icon"
src="{{pressedSvgButtonKey === 'records:close-edit' ? (accentIcons.cancel || icons.cancel || '/assets/icons/cancel.svg') : (icons.cancel || '/assets/icons/cancel.svg')}}"
mode="aspectFit"
/>
</button>
</view>
</view>
<bottom-nav page="records" />
</view>

View File

@@ -0,0 +1,498 @@
.records-page .page-content {
padding-top: 16rpx;
}
.records-panel {
padding: 0 0 16rpx;
gap: 16rpx;
}
.records-search-wrap {
position: relative;
z-index: 3;
}
.records-search-shell {
display: flex;
align-items: center;
width: 100%;
min-width: 0;
height: 64rpx;
border: 1rpx solid var(--surface-border);
border-radius: 54rpx;
overflow: hidden;
background: var(--surface);
}
.records-search-input {
flex: 1;
min-width: 0;
height: 100%;
border: 0;
border-radius: 0;
background: transparent;
color: var(--text);
font-size: 22rpx;
line-height: normal;
padding: 0 16rpx;
}
.records-search-input-placeholder {
color: var(--muted);
}
.records-filter-btn {
flex: 0 0 88rpx;
width: 88rpx !important;
min-width: 88rpx !important;
height: 100% !important;
margin: 0 !important;
border: 0 !important;
border-left: 1rpx solid var(--surface-border);
border-radius: 0 !important;
background: var(--bg) !important;
color: var(--text) !important;
padding: 0 16rpx !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
}
.records-filter-arrow {
flex: 0 0 auto;
font-size: 22rpx;
line-height: 1;
}
.records-filter-menu {
margin-top: 12rpx;
padding: 10rpx;
border: 1rpx solid var(--surface-border);
border-radius: 18rpx;
background: var(--surface);
display: flex;
flex-wrap: wrap;
gap: 10rpx;
box-shadow: 0 14rpx 30rpx var(--surface-shadow);
}
.records-filter-option {
padding: 10rpx 18rpx;
border-radius: 999rpx;
border: 1rpx solid var(--surface-border);
background: var(--bg);
color: var(--muted);
font-size: 22rpx;
line-height: 1.1;
}
.records-filter-option.active {
border-color: rgba(91, 210, 255, 0.78);
background: rgba(91, 210, 255, 0.18);
color: var(--text);
font-weight: 700;
}
.records-list-scroll {
flex: 1;
min-height: 0;
}
.records-list {
min-height: 0;
padding-bottom: 8rpx;
}
.record-item-shell {
position: relative;
overflow: hidden;
border-radius: 20rpx;
transition: opacity 160ms ease;
}
.record-item-shell-muted {
opacity: 0.6;
}
.record-item-actions-wrap {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 240rpx;
display: flex;
flex-wrap: nowrap;
gap: 0;
z-index: 0;
opacity: 0;
pointer-events: none;
transition: opacity 160ms ease;
}
.record-item-actions-wrap.opened {
opacity: 1;
pointer-events: auto;
}
.record-swipe-copy-btn,
.record-swipe-delete-btn,
.record-swipe-processed-btn,
.record-swipe-discarded-btn {
width: 25% !important;
flex: 1 1 0;
height: 100% !important;
min-width: 0 !important;
margin: 0 !important;
border: 0 !important;
border-radius: 0 !important;
color: #f7fbff !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
font-size: 26rpx !important;
line-height: 1 !important;
padding: 0 !important;
}
.record-swipe-copy-btn {
border-radius: 20rpx 0 0 20rpx !important;
background: rgba(101, 130, 149, 0.8) !important;
}
.record-swipe-delete-btn {
border-radius: 0 20rpx 20rpx 0 !important;
background: rgba(164, 118, 118, 0.8) !important;
}
.record-swipe-processed-btn {
border-radius: 0 !important;
background: rgba(124, 145, 114, 0.8) !important;
}
.record-swipe-discarded-btn {
border-radius: 0 !important;
background: rgba(118, 124, 136, 0.8) !important;
}
.record-swipe-btn-text {
/* 左滑动作区维持竖排文案,适配四个窄按钮且不引入额外 gap。 */
writing-mode: vertical-rl;
text-orientation: upright;
letter-spacing: 2rpx;
font-size: 22rpx;
line-height: 1.05;
font-weight: 600;
}
.record-item-track {
position: relative;
z-index: 1;
transition: transform 160ms ease;
will-change: transform;
}
.record-item-main {
display: flex;
align-items: stretch;
gap: 2rpx;
}
.record-item-category-hitbox {
/* 分类条视觉宽度不变,只把横向热区扩到约 2 倍。 */
flex: 0 0 auto;
display: inline-flex;
align-items: stretch;
padding: 0 24rpx;
margin: 0 -24rpx;
position: relative;
z-index: 1;
}
.record-item-category {
flex: 0 0 auto;
width: 48rpx;
min-width: 48rpx;
border: 1rpx solid transparent;
border-radius: 18rpx 0 0 18rpx;
display: inline-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 10rpx 6rpx;
}
.record-item-category-text {
color: var(--text);
font-size: 20rpx;
line-height: 1.1;
text-align: center;
writing-mode: vertical-rl;
text-orientation: mixed;
}
.record-item {
flex: 1;
min-width: 0;
border-radius: 0 18rpx 18rpx 0;
}
.record-item-processed {
/* 已处理态改成更亮的浅绿底,拉开与深绿正文的反差,避免在移动端上糊成一片。 */
background: linear-gradient(180deg, rgba(226, 238, 213, 0.96), rgba(206, 224, 188, 0.94));
border-color: rgba(109, 136, 95, 0.62);
}
.record-item-processed .record-item-time,
.record-item-processed .record-item-context {
color: #58714f;
}
.record-item-processed .record-item-content {
color: #34513a;
}
.record-item-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12rpx;
}
.record-item-time {
flex: 0 0 auto;
font-size: 22rpx;
color: var(--muted);
}
.record-item-context {
flex: 1;
min-width: 0;
font-size: 22rpx;
color: var(--muted);
text-align: right;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.record-item-content {
display: block;
margin-top: 10rpx;
color: var(--text);
white-space: pre-wrap;
word-break: break-all;
line-height: 1.6;
font-size: 24rpx;
}
.record-item-content-discarded {
text-decoration: line-through;
text-decoration-thickness: 2rpx;
}
.records-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
}
.records-footer-actions {
display: inline-flex;
align-items: center;
gap: 12rpx;
}
.records-footer-action-btn {
display: inline-flex !important;
align-items: center;
justify-content: center;
gap: 8rpx;
--svg-press-active-radius: 16rpx;
--svg-press-active-bg: var(--btn-bg-active);
--svg-press-active-shadow: none;
--svg-press-active-scale: 1;
--svg-press-icon-opacity: 0.96;
--svg-press-icon-active-opacity: 0.72;
--svg-press-icon-active-scale: 0.92;
}
.records-footer-action-icon {
width: 26rpx;
height: 26rpx;
display: block;
}
.records-pagination {
display: flex;
align-items: center;
gap: 12rpx;
}
.records-pagination-text {
font-size: 22rpx;
color: var(--muted);
}
.records-edit-mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 40;
background: rgba(5, 11, 24, 0.54);
display: flex;
align-items: center;
justify-content: center;
padding: 32rpx;
box-sizing: border-box;
}
.records-quick-mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 38;
background: rgba(5, 11, 24, 0.2);
}
.records-quick-panel {
position: absolute;
padding: 12rpx;
border-radius: 24rpx;
background: rgba(13, 24, 42, 0.2);
backdrop-filter: blur(10px);
}
.records-quick-cloud {
position: relative;
}
.records-quick-bubble {
position: absolute;
display: inline-flex !important;
align-items: center;
justify-content: center;
margin: 0 !important;
border: 1rpx solid transparent !important;
border-radius: 999rpx !important;
padding: 0 !important;
color: var(--text) !important;
font-size: 20rpx !important;
line-height: 1.15 !important;
font-weight: 700 !important;
text-align: center;
white-space: normal;
overflow-wrap: anywhere;
box-shadow: 0 10rpx 24rpx rgba(15, 35, 68, 0.18);
}
.records-quick-bubble.active {
box-shadow:
0 0 0 2rpx rgba(255, 255, 255, 0.18),
0 10rpx 24rpx rgba(42, 92, 182, 0.24);
transform: scale(1.05);
}
.records-edit-panel {
position: relative;
width: 100%;
max-width: 680rpx;
border-radius: 28rpx;
background: var(--surface);
border: 1rpx solid var(--surface-border);
padding: 24rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 16rpx;
box-shadow: 0 18rpx 42rpx var(--surface-shadow);
}
.records-edit-category-scroll {
width: calc(100% - 56rpx);
white-space: nowrap;
}
.records-edit-category-row {
display: inline-flex;
align-items: center;
gap: 10rpx;
min-width: 100%;
}
.records-edit-category-pill {
flex: 0 0 auto;
min-height: 52rpx;
padding: 8rpx 18rpx;
border-radius: 999rpx;
border: 1rpx solid var(--surface-border);
background: var(--bg);
color: var(--muted);
font-size: 22rpx;
line-height: 1.1;
display: inline-flex;
align-items: center;
justify-content: center;
white-space: nowrap;
}
.records-edit-category-pill.active {
border-color: rgba(91, 210, 255, 0.78);
background: rgba(91, 210, 255, 0.18);
color: var(--text);
font-weight: 700;
}
.records-edit-textarea {
width: 100%;
min-height: 240rpx;
border: 1rpx solid var(--surface-border);
border-radius: 20rpx;
background: var(--bg);
color: var(--text);
font-size: 26rpx;
line-height: 1.6;
box-sizing: border-box;
padding: 18rpx 20rpx;
}
.records-edit-time {
font-size: 22rpx;
color: var(--muted);
padding-right: 48rpx;
}
.records-edit-close-btn {
position: absolute;
right: 24rpx;
top: 24rpx;
width: 40rpx !important;
min-width: 40rpx !important;
height: 40rpx !important;
margin: 0 !important;
border: 0 !important;
border-radius: 999rpx !important;
background: transparent !important;
padding: 0 !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
--svg-press-active-radius: 999rpx;
--svg-press-active-bg: rgba(156, 169, 191, 0.24);
--svg-press-active-shadow:
inset 0 0 0 1rpx rgba(210, 220, 236, 0.34),
0 0 0 8rpx rgba(156, 169, 191, 0.12);
--svg-press-active-scale: 0.9;
--svg-press-icon-opacity: 0.96;
--svg-press-icon-active-opacity: 0.68;
--svg-press-icon-active-scale: 0.88;
}
.records-edit-close-icon {
width: 32rpx;
height: 32rpx;
}