思源界面主题色跟随当前题头图变化的 js 片段
目前有按钮等变化,其他变化自行添加 css 容器
/*! Fast Average Color | © 2025 Denis Seleznev | MIT License | https://github.com/fast-average-color/fast-average-color */
'use strict';
function toHex(num) {
var str = num.toString(16);
return str.length === 1 ? '0' + str : str;
}
function arrayToHex(arr) {
return '#' + arr.map(toHex).join('');
}
function isDark(color) {
// http://www.w3.org/TR/AERT#color-contrast
var result = (color[0] * 299 + color[1] * 587 + color[2] * 114) / 1000;
return result < 128;
}
function prepareIgnoredColor(color) {
if (!color) {
return [];
}
return isRGBArray(color) ? color : [color];
}
function isRGBArray(value) {
return Array.isArray(value[0]);
}
function isIgnoredColor(data, index, ignoredColor) {
for (var i = 0; i < ignoredColor.length; i++) {
if (isIgnoredColorAsNumbers(data, index, ignoredColor[i])) {
return true;
}
}
return false;
}
function isIgnoredColorAsNumbers(data, index, ignoredColor) {
switch (ignoredColor.length) {
case 3:
// [red, green, blue]
if (isIgnoredRGBColor(data, index, ignoredColor)) {
return true;
}
break;
case 4:
// [red, green, blue, alpha]
if (isIgnoredRGBAColor(data, index, ignoredColor)) {
return true;
}
break;
case 5:
// [red, green, blue, alpha, threshold]
if (isIgnoredRGBAColorWithThreshold(data, index, ignoredColor)) {
return true;
}
break;
default:
return false;
}
}
function isIgnoredRGBColor(data, index, ignoredColor) {
// Ignore if the pixel are transparent.
if (data[index + 3] !== 255) {
return true;
}
if (data[index] === ignoredColor[0] &&
data[index + 1] === ignoredColor[1] &&
data[index + 2] === ignoredColor[2]) {
return true;
}
return false;
}
function isIgnoredRGBAColor(data, index, ignoredColor) {
if (data[index + 3] && ignoredColor[3]) {
return data[index] === ignoredColor[0] &&
data[index + 1] === ignoredColor[1] &&
data[index + 2] === ignoredColor[2] &&
data[index + 3] === ignoredColor[3];
}
// Ignore rgb components if the pixel are fully transparent.
return data[index + 3] === ignoredColor[3];
}
function inRange(colorComponent, ignoredColorComponent, value) {
return colorComponent >= (ignoredColorComponent - value) &&
colorComponent <= (ignoredColorComponent + value);
}
function isIgnoredRGBAColorWithThreshold(data, index, ignoredColor) {
var redIgnored = ignoredColor[0];
var greenIgnored = ignoredColor[1];
var blueIgnored = ignoredColor[2];
var alphaIgnored = ignoredColor[3];
var threshold = ignoredColor[4];
var alphaData = data[index + 3];
var alphaInRange = inRange(alphaData, alphaIgnored, threshold);
if (!alphaIgnored) {
return alphaInRange;
}
if (!alphaData && alphaInRange) {
return true;
}
if (inRange(data[index], redIgnored, threshold) &&
inRange(data[index + 1], greenIgnored, threshold) &&
inRange(data[index + 2], blueIgnored, threshold) &&
alphaInRange) {
return true;
}
return false;
}
var DEFAULT_DOMINANT_DIVIDER = 24;
function dominantAlgorithm(arr, len, options) {
var colorHash = {};
var divider = options.dominantDivider || DEFAULT_DOMINANT_DIVIDER;
var ignoredColor = options.ignoredColor;
var step = options.step;
var max = [0, 0, 0, 0, 0];
for (var i = 0; i < len; i += step) {
var red = arr[i];
var green = arr[i + 1];
var blue = arr[i + 2];
var alpha = arr[i + 3];
if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) {
continue;
}
var key = Math.round(red / divider) + ',' +
Math.round(green / divider) + ',' +
Math.round(blue / divider);
if (colorHash[key]) {
colorHash[key] = [
colorHash[key][0] + red * alpha,
colorHash[key][1] + green * alpha,
colorHash[key][2] + blue * alpha,
colorHash[key][3] + alpha,
colorHash[key][4] + 1
];
}
else {
colorHash[key] = [red * alpha, green * alpha, blue * alpha, alpha, 1];
}
if (max[4] < colorHash[key][4]) {
max = colorHash[key];
}
}
var redTotal = max[0];
var greenTotal = max[1];
var blueTotal = max[2];
var alphaTotal = max[3];
var count = max[4];
return alphaTotal ? [
Math.round(redTotal / alphaTotal),
Math.round(greenTotal / alphaTotal),
Math.round(blueTotal / alphaTotal),
Math.round(alphaTotal / count)
] : options.defaultColor;
}
function simpleAlgorithm(arr, len, options) {
var redTotal = 0;
var greenTotal = 0;
var blueTotal = 0;
var alphaTotal = 0;
var count = 0;
var ignoredColor = options.ignoredColor;
var step = options.step;
for (var i = 0; i < len; i += step) {
var alpha = arr[i + 3];
var red = arr[i] * alpha;
var green = arr[i + 1] * alpha;
var blue = arr[i + 2] * alpha;
if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) {
continue;
}
redTotal += red;
greenTotal += green;
blueTotal += blue;
alphaTotal += alpha;
count++;
}
return alphaTotal ? [
Math.round(redTotal / alphaTotal),
Math.round(greenTotal / alphaTotal),
Math.round(blueTotal / alphaTotal),
Math.round(alphaTotal / count)
] : options.defaultColor;
}
function sqrtAlgorithm(arr, len, options) {
var redTotal = 0;
var greenTotal = 0;
var blueTotal = 0;
var alphaTotal = 0;
var count = 0;
var ignoredColor = options.ignoredColor;
var step = options.step;
for (var i = 0; i < len; i += step) {
var red = arr[i];
var green = arr[i + 1];
var blue = arr[i + 2];
var alpha = arr[i + 3];
if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) {
continue;
}
redTotal += red * red * alpha;
greenTotal += green * green * alpha;
blueTotal += blue * blue * alpha;
alphaTotal += alpha;
count++;
}
return alphaTotal ? [
Math.round(Math.sqrt(redTotal / alphaTotal)),
Math.round(Math.sqrt(greenTotal / alphaTotal)),
Math.round(Math.sqrt(blueTotal / alphaTotal)),
Math.round(alphaTotal / count)
] : options.defaultColor;
}
function getDefaultColor(options) {
return getOption(options, 'defaultColor', [0, 0, 0, 0]);
}
function getOption(options, name, defaultValue) {
return (options[name] === undefined ? defaultValue : options[name]);
}
var MIN_SIZE = 10;
var MAX_SIZE = 100;
function isSvg(filename) {
return filename.search(/\.svg(\?|$)/i) !== -1;
}
function getOriginalSize(resource) {
if (isInstanceOfHTMLImageElement(resource)) {
var width = resource.naturalWidth;
var height = resource.naturalHeight;
// For SVG images with only viewBox attribute
if (!resource.naturalWidth && isSvg(resource.src)) {
width = height = MAX_SIZE;
}
return {
width: width,
height: height,
};
}
if (isInstanceOfHTMLVideoElement(resource)) {
return {
width: resource.videoWidth,
height: resource.videoHeight
};
}
if (isInstanceOfVideoFrame(resource)) {
return {
width: resource.codedWidth,
height: resource.codedHeight,
};
}
return {
width: resource.width,
height: resource.height
};
}
function getSrc(resource) {
if (isInstanceOfHTMLCanvasElement(resource)) {
return 'canvas';
}
if (isInstanceOfOffscreenCanvas(resource)) {
return 'offscreencanvas';
}
if (isInstanceOfVideoFrame(resource)) {
return 'videoframe';
}
if (isInstanceOfImageBitmap(resource)) {
return 'imagebitmap';
}
return resource.src;
}
function isInstanceOfHTMLImageElement(resource) {
return typeof HTMLImageElement !== 'undefined' && resource instanceof HTMLImageElement;
}
var hasOffscreenCanvas = typeof OffscreenCanvas !== 'undefined';
function isInstanceOfOffscreenCanvas(resource) {
return hasOffscreenCanvas && resource instanceof OffscreenCanvas;
}
function isInstanceOfHTMLVideoElement(resource) {
return typeof HTMLVideoElement !== 'undefined' && resource instanceof HTMLVideoElement;
}
function isInstanceOfVideoFrame(resource) {
return typeof VideoFrame !== 'undefined' && resource instanceof VideoFrame;
}
function isInstanceOfHTMLCanvasElement(resource) {
return typeof HTMLCanvasElement !== 'undefined' && resource instanceof HTMLCanvasElement;
}
function isInstanceOfImageBitmap(resource) {
return typeof ImageBitmap !== 'undefined' && resource instanceof ImageBitmap;
}
function prepareSizeAndPosition(originalSize, options) {
var srcLeft = getOption(options, 'left', 0);
var srcTop = getOption(options, 'top', 0);
var srcWidth = getOption(options, 'width', originalSize.width);
var srcHeight = getOption(options, 'height', originalSize.height);
var destWidth = srcWidth;
var destHeight = srcHeight;
if (options.mode === 'precision') {
return {
srcLeft: srcLeft,
srcTop: srcTop,
srcWidth: srcWidth,
srcHeight: srcHeight,
destWidth: destWidth,
destHeight: destHeight
};
}
var factor;
if (srcWidth > srcHeight) {
factor = srcWidth / srcHeight;
destWidth = MAX_SIZE;
destHeight = Math.round(destWidth / factor);
}
else {
factor = srcHeight / srcWidth;
destHeight = MAX_SIZE;
destWidth = Math.round(destHeight / factor);
}
if (destWidth > srcWidth || destHeight > srcHeight ||
destWidth < MIN_SIZE || destHeight < MIN_SIZE) {
destWidth = srcWidth;
destHeight = srcHeight;
}
return {
srcLeft: srcLeft,
srcTop: srcTop,
srcWidth: srcWidth,
srcHeight: srcHeight,
destWidth: destWidth,
destHeight: destHeight
};
}
var isWebWorkers = typeof window === 'undefined';
function makeCanvas() {
if (isWebWorkers) {
return hasOffscreenCanvas ? new OffscreenCanvas(1, 1) : null;
}
return document.createElement('canvas');
}
var ERROR_PREFIX = 'FastAverageColor: ';
function getError(message) {
return Error(ERROR_PREFIX + message);
}
function outputError(error, silent) {
if (!silent) {
console.error(error);
}
}
var FastAverageColor = /** @class */ (function () {
function FastAverageColor() {
this.canvas = null;
this.ctx = null;
}
FastAverageColor.prototype.getColorAsync = function (resource, options) {
if (!resource) {
return Promise.reject(getError('call .getColorAsync() without resource'));
}
if (typeof resource === 'string') {
// Web workers
if (typeof Image === 'undefined') {
return Promise.reject(getError('resource as string is not supported in this environment'));
}
var img = new Image();
img.crossOrigin = options && options.crossOrigin || '';
img.src = resource;
return this.bindImageEvents(img, options);
}
else if (isInstanceOfHTMLImageElement(resource) && !resource.complete) {
return this.bindImageEvents(resource, options);
}
else {
var result = this.getColor(resource, options);
return result.error ? Promise.reject(result.error) : Promise.resolve(result);
}
};
/**
* Get the average color from images, videos and canvas.
*/
FastAverageColor.prototype.getColor = function (resource, options) {
options = options || {};
var defaultColor = getDefaultColor(options);
if (!resource) {
var error = getError('call .getColor() without resource');
outputError(error, options.silent);
return this.prepareResult(defaultColor, error);
}
var originalSize = getOriginalSize(resource);
var size = prepareSizeAndPosition(originalSize, options);
if (!size.srcWidth || !size.srcHeight || !size.destWidth || !size.destHeight) {
var error = getError("incorrect sizes for resource \"".concat(getSrc(resource), "\""));
outputError(error, options.silent);
return this.prepareResult(defaultColor, error);
}
if (!this.canvas) {
this.canvas = makeCanvas();
if (!this.canvas) {
var error = getError('OffscreenCanvas is not supported in this browser');
outputError(error, options.silent);
return this.prepareResult(defaultColor, error);
}
}
if (!this.ctx) {
this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
if (!this.ctx) {
var error = getError('Canvas Context 2D is not supported in this browser');
outputError(error, options.silent);
return this.prepareResult(defaultColor);
}
this.ctx.imageSmoothingEnabled = false;
}
this.canvas.width = size.destWidth;
this.canvas.height = size.destHeight;
try {
this.ctx.clearRect(0, 0, size.destWidth, size.destHeight);
this.ctx.drawImage(resource, size.srcLeft, size.srcTop, size.srcWidth, size.srcHeight, 0, 0, size.destWidth, size.destHeight);
var bitmapData = this.ctx.getImageData(0, 0, size.destWidth, size.destHeight).data;
return this.prepareResult(this.getColorFromArray4(bitmapData, options));
}
catch (originalError) {
var error = getError("security error (CORS) for resource ".concat(getSrc(resource), ".\nDetails: https://developer.mozilla.org/en/docs/Web/HTML/CORS_enabled_image"));
outputError(error, options.silent);
if (!options.silent) {
console.error(originalError);
}
return this.prepareResult(defaultColor, error);
}
};
/**
* Get the average color from a array when 1 pixel is 4 bytes.
*/
FastAverageColor.prototype.getColorFromArray4 = function (arr, options) {
options = options || {};
var bytesPerPixel = 4;
var arrLength = arr.length;
var defaultColor = getDefaultColor(options);
if (arrLength < bytesPerPixel) {
return defaultColor;
}
var len = arrLength - arrLength % bytesPerPixel;
var step = (options.step || 1) * bytesPerPixel;
var algorithm;
switch (options.algorithm || 'sqrt') {
case 'simple':
algorithm = simpleAlgorithm;
break;
case 'sqrt':
algorithm = sqrtAlgorithm;
break;
case 'dominant':
algorithm = dominantAlgorithm;
break;
default:
throw getError("".concat(options.algorithm, " is unknown algorithm"));
}
return algorithm(arr, len, {
defaultColor: defaultColor,
ignoredColor: prepareIgnoredColor(options.ignoredColor),
step: step,
dominantDivider: options.dominantDivider,
});
};
/**
* Get color data from value ([r, g, b, a]).
*/
FastAverageColor.prototype.prepareResult = function (value, error) {
var rgb = value.slice(0, 3);
var rgba = [value[0], value[1], value[2], value[3] / 255];
var isDarkColor = isDark(value);
return {
value: [value[0], value[1], value[2], value[3]],
rgb: 'rgb(' + rgb.join(',') + ')',
rgba: 'rgba(' + rgba.join(',') + ')',
hex: arrayToHex(rgb),
hexa: arrayToHex(value),
isDark: isDarkColor,
isLight: !isDarkColor,
error: error,
};
};
/**
* Destroy the instance.
*/
FastAverageColor.prototype.destroy = function () {
if (this.canvas) {
this.canvas.width = 1;
this.canvas.height = 1;
this.canvas = null;
}
this.ctx = null;
};
FastAverageColor.prototype.bindImageEvents = function (resource, options) {
var _this = this;
return new Promise(function (resolve, reject) {
var onload = function () {
unbindEvents();
var result = _this.getColor(resource, options);
if (result.error) {
reject(result.error);
}
else {
resolve(result);
}
};
var onerror = function () {
unbindEvents();
reject(getError("Error loading image \"".concat(resource.src, "\"")));
};
var onabort = function () {
unbindEvents();
reject(getError("Image \"".concat(resource.src, "\" loading aborted")));
};
var unbindEvents = function () {
resource.removeEventListener('load', onload);
resource.removeEventListener('error', onerror);
resource.removeEventListener('abort', onabort);
};
resource.addEventListener('load', onload);
resource.addEventListener('error', onerror);
resource.addEventListener('abort', onabort);
});
};
return FastAverageColor;
}());
if (typeof window !== 'undefined') {
window.FastAverageColor = FastAverageColor;
}
// ==================================================
// 你的 CoverThemeColor 类 (已修改)
// ==================================================
class CoverThemeColor {
constructor() {
this.fac = null;
this.currentProtyle = null;
this.backgroundObserver = null;
this.tabObserver = null;
this.lastProcessedUrl = '';
this.debounceTimer = null;
// 直接实例化内嵌的 FastAverageColor
if (window.FastAverageColor) {
this.fac = new window.FastAverageColor();
console.log('CoverThemeColor: (诊断) FastAverageColor 已内嵌并实例化。');
} else {
console.warn('CoverThemeColor: (诊断) 内嵌 FastAverageColor 失败。图片题头图功能将不可用。');
}
this.init();
}
init() {
// 不再需要 loadScript
this.findTabBar();
}
findTabBar() {
const tabBar = document.querySelector('.layout__center .layout-tab-bar');
if (tabBar) {
console.log('CoverThemeColor (诊断): 成功找到 Tab 栏, 启动监视器。');
this.setupTabObserver(tabBar);
this.handleTabSwitch();
} else {
setTimeout(() => this.findTabBar(), 200);
}
}
setupTabObserver(tabBar) {
this.tabObserver = new MutationObserver(() => {
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => this.handleTabSwitch(), 150);
});
this.tabObserver.observe(tabBar, {
attributes: true,
attributeFilter: ['class'],
subtree: true
});
}
handleTabSwitch() {
const focusedTab = document.querySelector('.layout__center .layout-tab-bar .item--focus');
if (!focusedTab) {
console.log('CoverThemeColor (诊断): handleTabSwitch - 未找到 focused tab (.item--focus)');
return;
}
const protyleId = focusedTab.dataset.id;
if (!protyleId) {
console.log('CoverThemeColor (诊断): handleTabSwitch - Focused tab 没有 data-id');
return;
}
const protyleElement = document.querySelector(`.layout__center .protyle[data-id="${protyleId}"]`);
if (!protyleElement) {
console.log(`CoverThemeColor (诊断): handleTabSwitch - 未找到 data-id 为 ${protyleId} 的 protyle`);
return;
}
if (protyleElement === this.currentProtyle) {
return;
}
console.log(`CoverThemeColor (诊断): handleTabSwitch - 检测到文档切换! 新 ID: ${protyleId}`);
this.currentProtyle = protyleElement;
this.setupBackgroundObserver(protyleElement);
}
setupBackgroundObserver(protyleElement) {
if (this.backgroundObserver) {
this.backgroundObserver.disconnect();
this.backgroundObserver = null;
}
const backgroundEl = protyleElement.querySelector('.protyle-background');
if (backgroundEl) {
this.updateColor();
this.backgroundObserver = new MutationObserver(() => {
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => this.updateColor(), 150);
});
this.backgroundObserver.observe(backgroundEl, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['src', 'style']
});
} else {
this.resetColor();
}
}
// loadScript 方法已被移除
updateColor() {
if (!this.currentProtyle) {
console.log('CoverThemeColor (诊断): updateColor - 失败, this.currentProtyle 为空。');
return;
}
const img = this.currentProtyle.querySelector('.protyle-background__img img');
if (!img) {
this.resetColor();
return;
}
const style = img.getAttribute('style');
if (style && style.includes('linear-gradient')) {
if (style === this.lastProcessedUrl) return;
console.log('CoverThemeColor (诊断): updateColor - 找到渐变色 (linear-gradient)。');
const gradientRegex = /linear-gradient\([^,]+,([^,)]+)/;
const colorMatch = style.match(gradientRegex);
if (colorMatch && colorMatch[1]) {
const colorRegex = /(#[0-9a-fA-F]{3,6}|rgba?\([^)]+\))/;
const finalColorMatch = colorMatch[1].match(colorRegex);
if (finalColorMatch && finalColorMatch[1]) {
const color = finalColorMatch[1].trim();
console.log(`CoverThemeColor (诊断): 提取到渐变色: ${color}`);
this.applyColor(color);
this.lastProcessedUrl = style;
return;
}
}
console.log('CoverThemeColor (诊断): 找到了渐变色, 但提取颜色失败。');
}
if (!this.fac) {
if (img.src && img.src.length > 200) {
console.log('CoverThemeColor (诊断): updateColor - 找到图片, 但 fac 未加载, 无法处理。');
}
this.resetColor();
return;
}
if (img.src) {
if (img.src === this.lastProcessedUrl) {
return;
}
if (img.complete) {
this.processImage(img);
} else {
img.onload = () => this.processImage(img);
img.onerror = () => {
console.error('CoverThemeColor: 图片加载失败。', img.src);
this.lastProcessedUrl = img.src;
};
}
} else {
this.resetColor();
}
}
processImage(img) {
if (!img || !img.src || (img.src.startsWith('data:image/') && img.src.length < 200)) {
if (this.lastProcessedUrl !== img.src) {
this.resetColor();
}
this.lastProcessedUrl = img.src;
return;
}
this.lastProcessedUrl = img.src;
console.log(`CoverThemeColor (诊断): processImage - 正在处理图片: ${img.src.substring(0, 50)}...`);
this.fac.getColorAsync(img)
.then(color => {
if (color) {
this.applyColor(color.hex);
}
})
.catch(e => {
console.error('CoverThemeColor: 提取颜色失败 (fac.getColorAsync 失败)。', e);
this.resetColor();
});
}
applyColor(color) {
console.log(`CoverThemeColor (诊断): applyColor - 正在应用颜色: ${color}`);
const rootStyle = document.documentElement.style;
rootStyle.setProperty('--b3-theme-primary', color);
let hexColor = color;
if (color.startsWith('rgb')) {
hexColor = this.rgbToHex(color);
}
if (!hexColor || !hexColor.startsWith('#') || hexColor.length < 4) {
console.warn('CoverThemeColor: 无法计算变体, 颜色不是有效的Hex或RGB:', color);
rootStyle.removeProperty('--b3-theme-primary-light');
rootStyle.removeProperty('--b3-theme-primary-dark');
return;
}
try {
const lightColor = this.adjustColor(hexColor, 20);
const darkColor = this.adjustColor(hexColor, -20);
rootStyle.setProperty('--b3-theme-primary-light', lightColor);
rootStyle.setProperty('--b3-theme-primary-dark', darkColor);
} catch (e) {
console.error('CoverThemeColor: 计算亮/暗变体失败。', e);
}
}
resetColor() {
if (this.lastProcessedUrl !== '') {
console.log('CoverThemeColor (诊断): resetColor - 正在重置颜色。');
}
this.lastProcessedUrl = '';
const rootStyle = document.documentElement.style;
rootStyle.removeProperty('--b3-theme-primary');
rootStyle.removeProperty('--b3-theme-primary-light');
rootStyle.removeProperty('--b3-theme-primary-dark');
}
adjustColor(color, percent) {
if (color.length === 4) {
color = `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`;
}
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
const amount = Math.round(2.55 * percent);
r = Math.max(0, Math.min(255, r + amount));
g = Math.max(0, Math.min(255, g + amount));
b = Math.max(0, Math.min(255, b + amount));
const rr = r.toString(16).padStart(2, '0');
const gg = g.toString(16).padStart(2, '0');
const bb = b.toString(16).padStart(2, '0');
return `#${rr}${gg}${bb}`;
}
rgbToHex(rgb) {
try {
const match = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (!match) return '#000000';
const componentToHex = (c) => {
const hex = parseInt(c, 10).toString(16);
return hex.length == 1 ? "0" + hex : hex;
};
return "#" + componentToHex(match[1]) + componentToHex(match[2]) + componentToHex(match[3]);
} catch (e) {
console.error('CoverThemeColor: rgbToHex 转换失败', e);
return '#000000';
}
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.coverThemeColorSnippet = new CoverThemeColor();
});
} else {
if (document.body) {
window.coverThemeColorSnippet = new CoverThemeColor();
} else {
document.addEventListener('DOMContentLoaded', () => {
window.coverThemeColorSnippet = new CoverThemeColor();
});
}
}
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于