修复全页面走查发现的问题(第四批:精准查找、隐私锁定、搜索防抖)
精准查找搜不到手机号(第5章 5.5)
- 一直写死 type=exact_account,而该分支只比对 profile.public_account / u.username / u.id
(tenant_adapter.go:9170-9172),**不匹配 u.phone**。参考图 5.5 里输的恰恰是手机号,
这个场景必然搜不到。改为按输入形态分流:11 位 1 开头走 type=phone。
- phone 分支服务端不做 E.164 归一化(只有模糊的 account 分支才调 normalizeUserPhone),
库里存的是 +86 开头,所以裸号码由前端补区号——注册流程本来也是这么拼的。
- 已用真实后端验证请求正确路由为 q=%2B8613800001188&type=phone。
隐私设置忽略 editable(第10章 10.12/10.13)
- 后端 userPrivacyPayload 返回 editable{add_me_methods, profile_visibility,
last_seen_visibility, read_receipts} 表达"企业强制策略锁死了这一项",前端完全没读。
被锁的开关照样能点,点了 400 再回滚,用户只看到开关自己弹回去。现在禁用并给出说明文案,
同时补 .personal-switch:disabled 的视觉反馈(否则看不出为什么点不动)。
搜索防抖
- 新增共享的 useDebouncedValue,用在三处真正发服务端请求的搜索:收藏、会话内检索、
群列表、群文件。原来 query 直接进 effect 依赖,每敲一个字符发一次请求;群列表更糟,
reload 对三个分类各发一次,一个字符就是 3 个请求。而且返回顺序没有保证,后发的短前缀
请求可能后到、把正确结果覆盖掉。
- 实测:群列表输入 4 个字,请求数从 12 降到 3。
- FavoriteShare 那处搜索是本地过滤 chatList、不发请求,没有动。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c9b8125013
commit
35d3e1cf6b
@@ -13019,6 +13019,13 @@ div.toast.show {
|
|||||||
background: #d8dce1;
|
background: #d8dce1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 企业强制策略锁死的项(后端 privacy.editable 里为 false)要看得出是不可改的,
|
||||||
|
否则用户只会觉得开关点不动。 */
|
||||||
|
.personal-switch:disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.personal-switch i {
|
.personal-switch i {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.15rem;
|
top: 0.15rem;
|
||||||
|
|||||||
@@ -53,3 +53,17 @@ export function readCachedData(key) {
|
|||||||
export function writeCachedData(key, value) {
|
export function writeCachedData(key, value) {
|
||||||
if (key) cache.set(key, value);
|
if (key) cache.set(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索框防抖:返回一个滞后于 `value` 的值,只有停止输入 `delay` 毫秒后才更新。
|
||||||
|
// 用途是把"每敲一个字符发一次请求"降成"停下来才发一次"。收藏搜索、会话内检索、群聊搜索
|
||||||
|
// 原来都是把 query 直接放进 effect 依赖里,输入 6 个字的关键词就打 6 次接口,
|
||||||
|
// 而且返回顺序没有保证——后发的短前缀请求可能后到,把正确结果覆盖掉。
|
||||||
|
export function useDebouncedValue(value, delay = 300) {
|
||||||
|
const [debounced, setDebounced] = React.useState(value);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (value === debounced) return undefined;
|
||||||
|
const timer = window.setTimeout(() => setDebounced(value), delay);
|
||||||
|
return () => window.clearTimeout(timer);
|
||||||
|
}, [value, delay, debounced]);
|
||||||
|
return debounced;
|
||||||
|
}
|
||||||
|
|||||||
@@ -195,6 +195,21 @@ function FriendRequests({onToast}) {
|
|||||||
</main></div>;
|
</main></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 中国大陆手机号:11 位、1 开头。与本文件注册/验证流程用的判断保持一致。
|
||||||
|
const CN_MOBILE = /^1[3-9]\d{9}$/;
|
||||||
|
|
||||||
|
function classifySearchInput(text) {
|
||||||
|
if (text.startsWith('+')) {
|
||||||
|
// 已经是国际格式,原样按手机号查
|
||||||
|
return {query: text.replace(/[\s-]/g, ''), type: 'phone'};
|
||||||
|
}
|
||||||
|
const digits = text.replace(/[\s-]/g, '');
|
||||||
|
if (CN_MOBILE.test(digits)) {
|
||||||
|
return {query: '+86' + digits, type: 'phone'};
|
||||||
|
}
|
||||||
|
return {query: text, type: 'exact_account'};
|
||||||
|
}
|
||||||
|
|
||||||
// 与 personal-settings-view.jsx 的 GENDER_OPTIONS 一一对应,两处写的是同一套后端枚举
|
// 与 personal-settings-view.jsx 的 GENDER_OPTIONS 一一对应,两处写的是同一套后端枚举
|
||||||
const GENDER_LABELS = {male: '男', female: '女', other: '其他'};
|
const GENDER_LABELS = {male: '男', female: '女', other: '其他'};
|
||||||
|
|
||||||
@@ -222,9 +237,16 @@ function AddFriend({result = false, onToast, onOpenScanner}) {
|
|||||||
const [searched, setSearched] = React.useState(result);
|
const [searched, setSearched] = React.useState(result);
|
||||||
const [results, setResults] = React.useState([]);
|
const [results, setResults] = React.useState([]);
|
||||||
const runSearch = q => {
|
const runSearch = q => {
|
||||||
if (!q.trim()) { setSearched(false); setResults([]); return; }
|
const text = q.trim();
|
||||||
|
if (!text) { setSearched(false); setResults([]); return; }
|
||||||
setSearching(true); setSearched(true);
|
setSearching(true); setSearched(true);
|
||||||
searchUsers(q.trim(), 'exact_account').then(data => setResults(data.items || [])).catch(() => setResults([])).finally(() => setSearching(false));
|
// 按输入形态选搜索类型。exact_account 只比对 profile.public_account / u.username / u.id
|
||||||
|
// (tenant_adapter.go:9170-9172),**不匹配 u.phone** —— 参考图 5.5 里输的恰恰是手机号
|
||||||
|
// 18826827724,一直写死 exact_account 的话这个场景必然搜不到。手机号要走 type=phone。
|
||||||
|
// 另外 phone 分支服务端不做 E.164 归一化(只有模糊的 account 分支才调 normalizeUserPhone),
|
||||||
|
// 而库里存的是 +86 开头的 E.164,所以裸 11 位号码由前端补上区号——注册流程也是这么拼的。
|
||||||
|
const {query, type} = classifySearchInput(text);
|
||||||
|
searchUsers(query, type).then(data => setResults(data.items || [])).catch(() => setResults([])).finally(() => setSearching(false));
|
||||||
};
|
};
|
||||||
const shareCard = () => {
|
const shareCard = () => {
|
||||||
createMyQRCode().then(data => {
|
createMyQRCode().then(data => {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
getFavorites, getFavorite, batchDeleteFavorites, forwardFavorites, createFileDownloader,
|
getFavorites, getFavorite, batchDeleteFavorites, forwardFavorites, createFileDownloader,
|
||||||
searchTopicMessages, getMessageContext, getTopicMedia, checkURL, getFileUrl, getGroup, getUserProfile
|
searchTopicMessages, getMessageContext, getTopicMedia, checkURL, getFileUrl, getGroup, getUserProfile
|
||||||
} from '../lib/im-api.js';
|
} from '../lib/im-api.js';
|
||||||
import { readCachedData, writeCachedData } from '../lib/use-cached-data.js';
|
import { readCachedData, writeCachedData, useDebouncedValue } from '../lib/use-cached-data.js';
|
||||||
import { topicDisplayName } from '../lib/utils.js';
|
import { topicDisplayName } from '../lib/utils.js';
|
||||||
|
|
||||||
// favoritePayload's source.topic is the raw conversation topic id (grpXXX/usrXXX) -- resolve it to a
|
// favoritePayload's source.topic is the raw conversation topic id (grpXXX/usrXXX) -- resolve it to a
|
||||||
@@ -173,7 +173,10 @@ function Favorites({view, tinode, onToast}) {
|
|||||||
const initialType = view === 'favorites-files' ? 'file' : view === 'favorites-text' ? 'text' : view === 'favorites-voice' ? 'voice' : view === 'favorites-links' ? 'link' : 'all';
|
const initialType = view === 'favorites-files' ? 'file' : view === 'favorites-text' ? 'text' : view === 'favorites-voice' ? 'voice' : view === 'favorites-links' ? 'link' : 'all';
|
||||||
const selecting = view === 'favorites-select';
|
const selecting = view === 'favorites-select';
|
||||||
const [type, setType] = React.useState(selecting ? 'file' : initialType);
|
const [type, setType] = React.useState(selecting ? 'file' : initialType);
|
||||||
const [query, setQuery] = React.useState('');
|
// 输入值与实际发请求用的值分开:原来 query 直接进 load 的依赖数组,输入 6 个字的关键词就打
|
||||||
|
// 6 次接口,而且返回顺序没有保证——后发的短前缀请求可能后到、把正确结果覆盖掉。
|
||||||
|
const [queryInput, setQueryInput] = React.useState('');
|
||||||
|
const query = useDebouncedValue(queryInput, 300);
|
||||||
const [selected, setSelected] = React.useState([]);
|
const [selected, setSelected] = React.useState([]);
|
||||||
// Seeded per-type -- one Favorites instance backs every favorites* tab/route (confirmed multiple
|
// Seeded per-type -- one Favorites instance backs every favorites* tab/route (confirmed multiple
|
||||||
// entry points: personal-settings' "我的收藏" row, direct-message-view.jsx's attachment-panel
|
// entry points: personal-settings' "我的收藏" row, direct-message-view.jsx's attachment-panel
|
||||||
@@ -245,7 +248,7 @@ function Favorites({view, tinode, onToast}) {
|
|||||||
return <div className="library-page">
|
return <div className="library-page">
|
||||||
<Header title={selecting ? `已选择 ${selected.length} 项` : '我的收藏'} back={selecting ? 'favorites-files' : 'messages-home'}
|
<Header title={selecting ? `已选择 ${selected.length} 项` : '我的收藏'} back={selecting ? 'favorites-files' : 'messages-home'}
|
||||||
right={selecting ? '取消' : '多选'} onRight={() => navigate(selecting ? 'favorites-files' : 'favorites-select')} />
|
right={selecting ? '取消' : '多选'} onRight={() => navigate(selecting ? 'favorites-files' : 'favorites-select')} />
|
||||||
<Search value={query} onChange={setQuery} placeholder="搜索收藏内容" />
|
<Search value={queryInput} onChange={setQueryInput} placeholder="搜索收藏内容" />
|
||||||
<nav className="library-type-tabs">{TYPE_LABELS.map(([id, label]) => <button type="button" key={id} className={type === id ? 'active' : ''}
|
<nav className="library-type-tabs">{TYPE_LABELS.map(([id, label]) => <button type="button" key={id} className={type === id ? 'active' : ''}
|
||||||
onClick={() => { setType(id); if (TYPE_ROUTE[id] && !selecting) navigate(TYPE_ROUTE[id]); }}>{label}</button>)}</nav>
|
onClick={() => { setType(id); if (TYPE_ROUTE[id] && !selecting) navigate(TYPE_ROUTE[id]); }}>{label}</button>)}</nav>
|
||||||
<main className="library-favorite-list">
|
<main className="library-favorite-list">
|
||||||
@@ -445,7 +448,10 @@ function History({view, hashParams, myUserId, onToast}) {
|
|||||||
// records (or checks a different chat's history later the same session), so dropping either half
|
// records (or checks a different chat's history later the same session), so dropping either half
|
||||||
// of the key would bleed one chat's or one tab's data into another's.
|
// of the key would bleed one chat's or one tab's data into another's.
|
||||||
const cacheKey = topic ? `history:${topic}:${view}` : null;
|
const cacheKey = topic ? `history:${topic}:${view}` : null;
|
||||||
const [query, setQuery] = React.useState('');
|
// 输入值与实际发请求用的值分开:原来 query 直接进 load 的依赖数组,输入 6 个字的关键词就打
|
||||||
|
// 6 次接口,而且返回顺序没有保证——后发的短前缀请求可能后到、把正确结果覆盖掉。
|
||||||
|
const [queryInput, setQueryInput] = React.useState('');
|
||||||
|
const query = useDebouncedValue(queryInput, 300);
|
||||||
const [items, setItems] = React.useState(() => (cacheKey && readCachedData(cacheKey)) || null);
|
const [items, setItems] = React.useState(() => (cacheKey && readCachedData(cacheKey)) || null);
|
||||||
const [cursor, setCursor] = React.useState('');
|
const [cursor, setCursor] = React.useState('');
|
||||||
const [hasMore, setHasMore] = React.useState(false);
|
const [hasMore, setHasMore] = React.useState(false);
|
||||||
@@ -513,7 +519,7 @@ function History({view, hashParams, myUserId, onToast}) {
|
|||||||
<Header title={HISTORY_TITLES[view]}
|
<Header title={HISTORY_TITLES[view]}
|
||||||
back={topic && topic.startsWith('grp') ? 'group-settings' : 'friend-settings'}
|
back={topic && topic.startsWith('grp') ? 'group-settings' : 'friend-settings'}
|
||||||
backParams={topic && topic.startsWith('grp') ? {topic} : {uid: topic}} />
|
backParams={topic && topic.startsWith('grp') ? {topic} : {uid: topic}} />
|
||||||
{view === 'history-search' ? <Search value={query} onChange={setQuery} placeholder="搜索当前会话" /> : null}
|
{view === 'history-search' ? <Search value={queryInput} onChange={setQueryInput} placeholder="搜索当前会话" /> : null}
|
||||||
<HistoryNav active={view} />
|
<HistoryNav active={view} />
|
||||||
<main className="library-history-content">
|
<main className="library-history-content">
|
||||||
{items === null ? <p className="library-empty-hint">加载中…</p> : !list.length ?
|
{items === null ? <p className="library-empty-hint">加载中…</p> : !list.length ?
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
deleteGroupAdmin, muteGroupMembers, getGroupMutes, unmuteGroupMember, transferGroupOwner,
|
deleteGroupAdmin, muteGroupMembers, getGroupMutes, unmuteGroupMember, transferGroupOwner,
|
||||||
getGroupFiles, addGroupFile, deleteGroupFile, uploadLargeFile, getFileUrl, getUserProfile
|
getGroupFiles, addGroupFile, deleteGroupFile, uploadLargeFile, getFileUrl, getUserProfile
|
||||||
} from '../lib/im-api.js';
|
} from '../lib/im-api.js';
|
||||||
import { readCachedData, writeCachedData } from '../lib/use-cached-data.js';
|
import { readCachedData, writeCachedData, useDebouncedValue } from '../lib/use-cached-data.js';
|
||||||
|
|
||||||
// Carries the group `topic` (and anything else already in the hash) across navigation, the same way
|
// Carries the group `topic` (and anything else already in the hash) across navigation, the same way
|
||||||
// contact-management-view.jsx threads `uid` -- these pages all need to know *which* group.
|
// contact-management-view.jsx threads `uid` -- these pages all need to know *which* group.
|
||||||
@@ -163,7 +163,10 @@ function GroupList({tinode, onToast}) {
|
|||||||
const [lists, setLists] = React.useState(() => readCachedData('groups') || {owned: null, managed: null, joined: null});
|
const [lists, setLists] = React.useState(() => readCachedData('groups') || {owned: null, managed: null, joined: null});
|
||||||
const [loadErrors, setLoadErrors] = React.useState({owned: false, managed: false, joined: false});
|
const [loadErrors, setLoadErrors] = React.useState({owned: false, managed: false, joined: false});
|
||||||
const [category, setCategory] = React.useState('owned');
|
const [category, setCategory] = React.useState('owned');
|
||||||
const [query, setQuery] = React.useState('');
|
// 这里比别处更值得防抖:reload 对 owned/managed/joined 三个分类各发一次请求,
|
||||||
|
// 每敲一个字符就是 3 个 getGroups。
|
||||||
|
const [queryInput, setQueryInput] = React.useState('');
|
||||||
|
const query = useDebouncedValue(queryInput, 300);
|
||||||
const [creating, setCreating] = React.useState(false);
|
const [creating, setCreating] = React.useState(false);
|
||||||
const [name, setName] = React.useState('');
|
const [name, setName] = React.useState('');
|
||||||
const [submitting, setSubmitting] = React.useState(false);
|
const [submitting, setSubmitting] = React.useState(false);
|
||||||
@@ -190,7 +193,7 @@ function GroupList({tinode, onToast}) {
|
|||||||
};
|
};
|
||||||
return <div className="group-page">
|
return <div className="group-page">
|
||||||
<Header title="群聊" back="address-book" right="创建" onRight={() => setCreating(true)} />
|
<Header title="群聊" back="address-book" right="创建" onRight={() => setCreating(true)} />
|
||||||
<Search value={query} onChange={setQuery} placeholder="搜索群聊" />
|
<Search value={queryInput} onChange={setQueryInput} placeholder="搜索群聊" />
|
||||||
<nav className="group-tabs">
|
<nav className="group-tabs">
|
||||||
{[['owned', '我创建的'], ['managed', '我管理的'], ['joined', '我加入的']].map(([key, label]) =>
|
{[['owned', '我创建的'], ['managed', '我管理的'], ['joined', '我加入的']].map(([key, label]) =>
|
||||||
<button type="button" key={key} className={category === key ? 'active' : ''} onClick={() => setCategory(key)}>
|
<button type="button" key={key} className={category === key ? 'active' : ''} onClick={() => setCategory(key)}>
|
||||||
@@ -847,7 +850,9 @@ const FILE_TYPE_META = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function GroupFiles({topic, tinode, onToast}) {
|
function GroupFiles({topic, tinode, onToast}) {
|
||||||
const [query, setQuery] = React.useState('');
|
// 群文件搜索走服务端 getGroupFiles({query}),同样防抖。
|
||||||
|
const [queryInput, setQueryInput] = React.useState('');
|
||||||
|
const query = useDebouncedValue(queryInput, 300);
|
||||||
// Only the unfiltered (query === '') result is cached -- same rule GroupList already follows, so a
|
// Only the unfiltered (query === '') result is cached -- same rule GroupList already follows, so a
|
||||||
// plain revisit never shows a stale search result under a fresh empty query.
|
// plain revisit never shows a stale search result under a fresh empty query.
|
||||||
const [files, setFiles] = React.useState(() => (topic && readCachedData('group-files:' + topic)) || null);
|
const [files, setFiles] = React.useState(() => (topic && readCachedData('group-files:' + topic)) || null);
|
||||||
@@ -902,7 +907,7 @@ function GroupFiles({topic, tinode, onToast}) {
|
|||||||
<button type="button" onClick={() => pick('image')}><Icon>image</Icon>上传照片</button>
|
<button type="button" onClick={() => pick('image')}><Icon>image</Icon>上传照片</button>
|
||||||
<button type="button" onClick={() => pick('video')}><Icon>play_circle_outline</Icon>上传视频</button>
|
<button type="button" onClick={() => pick('video')}><Icon>play_circle_outline</Icon>上传视频</button>
|
||||||
</div> : null}
|
</div> : null}
|
||||||
<Search value={query} onChange={setQuery} placeholder="搜索文件名或者上传者" />
|
<Search value={queryInput} onChange={setQueryInput} placeholder="搜索文件名或者上传者" />
|
||||||
<p className="group-inline-hint">共{(files || []).length}个文件(已使用{gb(usedBytes)}GB/10GB)</p>
|
<p className="group-inline-hint">共{(files || []).length}个文件(已使用{gb(usedBytes)}GB/10GB)</p>
|
||||||
<main className="group-file-list">
|
<main className="group-file-list">
|
||||||
{uploading ? <article className="uploading">
|
{uploading ? <article className="uploading">
|
||||||
|
|||||||
@@ -475,19 +475,23 @@ function PrivacySettings({onToast}) {
|
|||||||
if (on.length === 4) return '全部允许';
|
if (on.length === 4) return '全部允许';
|
||||||
return on.length ? on.join('、') : '全部关闭';
|
return on.length ? on.join('、') : '全部关闭';
|
||||||
};
|
};
|
||||||
|
// 后端 userPrivacyPayload 会返回 editable{add_me_methods, profile_visibility, last_seen_visibility,
|
||||||
|
// read_receipts},用来表达"企业强制策略锁死了这一项"。原来完全没读这个字段,被锁的开关照样能点,
|
||||||
|
// 点了服务端 400 再回滚,用户只看到开关自己弹回去、不知道为什么。
|
||||||
|
const locked = key => !!(privacy && privacy.editable && privacy.editable[key] === false);
|
||||||
return <div className="personal-page"><Header title="隐私设置" back="android-settings" /><main className="privacy-list">
|
return <div className="personal-page"><Header title="隐私设置" back="android-settings" /><main className="privacy-list">
|
||||||
<section><MenuRow title="添加我的方式" value={methodSummary()} route="friend-discovery-settings" /></section>
|
<section><MenuRow title="添加我的方式" value={methodSummary()} route="friend-discovery-settings" /></section>
|
||||||
{privacy ? <section>
|
{privacy ? <section>
|
||||||
<div><span><strong>显示消息已读状态</strong><small>关闭后你也无法查看对方的已读状态</small></span>
|
<div><span><strong>显示消息已读状态</strong><small>{locked('read_receipts') ? '该项由企业统一设置,不可修改' : '关闭后你也无法查看对方的已读状态'}</small></span>
|
||||||
<Toggle checked={!!privacy.read_receipts} onChange={v => update('read_receipts', v)} /></div>
|
<Toggle checked={!!privacy.read_receipts} disabled={locked('read_receipts')} onChange={v => update('read_receipts', v)} /></div>
|
||||||
<div><span><strong>展示最后在线时间</strong><small>仅向联系人显示</small></span>
|
<div><span><strong>展示最后在线时间</strong><small>{locked('last_seen_visibility') ? '该项由企业统一设置,不可修改' : '仅向联系人显示'}</small></span>
|
||||||
{/* Backend's validVisibility only accepts everyone/friends/nobody -- 'contacts'/'hidden' were
|
{/* Backend's validVisibility only accepts everyone/friends/nobody -- 'contacts'/'hidden' were
|
||||||
rejected by the PATCH validator outright (every toggle attempt 400'd and silently reverted),
|
rejected by the PATCH validator outright (every toggle attempt 400'd and silently reverted),
|
||||||
and even a lenient backend would enforce the opposite of what turning this "on" should do
|
and even a lenient backend would enforce the opposite of what turning this "on" should do
|
||||||
(imCanViewLastSeen's default case treats any unrecognized value as hidden). */}
|
(imCanViewLastSeen's default case treats any unrecognized value as hidden). */}
|
||||||
<Toggle checked={privacy.last_seen_visibility !== 'nobody'} onChange={v => update('last_seen_visibility', v ? 'friends' : 'nobody')} /></div>
|
<Toggle checked={privacy.last_seen_visibility !== 'nobody'} disabled={locked('last_seen_visibility')} onChange={v => update('last_seen_visibility', v ? 'friends' : 'nobody')} /></div>
|
||||||
<div><span><strong>允许好友查看我的资料</strong><small>关闭后仅你自己可见</small></span>
|
<div><span><strong>允许好友查看我的资料</strong><small>{locked('profile_visibility') ? '该项由企业统一设置,不可修改' : '关闭后仅你自己可见'}</small></span>
|
||||||
<Toggle checked={privacy.profile_visibility !== 'nobody'} onChange={v => update('profile_visibility', v ? 'friends' : 'nobody')} /></div>
|
<Toggle checked={privacy.profile_visibility !== 'nobody'} disabled={locked('profile_visibility')} onChange={v => update('profile_visibility', v ? 'friends' : 'nobody')} /></div>
|
||||||
</section> : null}
|
</section> : null}
|
||||||
</main></div>;
|
</main></div>;
|
||||||
}
|
}
|
||||||
@@ -505,11 +509,13 @@ function FriendDiscoverySettings({onToast}) {
|
|||||||
.catch(err => { onToast(err.message || '保存失败'); setPrivacy(prev); writeCachedData('me-privacy', prev); });
|
.catch(err => { onToast(err.message || '保存失败'); setPrivacy(prev); writeCachedData('me-privacy', prev); });
|
||||||
};
|
};
|
||||||
const rows = [{key: 'account', title: '账号'}, {key: 'phone', title: '手机号'}, {key: 'qr', title: '二维码'}, {key: 'group', title: '群聊'}];
|
const rows = [{key: 'account', title: '账号'}, {key: 'phone', title: '手机号'}, {key: 'qr', title: '二维码'}, {key: 'group', title: '群聊'}];
|
||||||
|
// 同 PrivacySettings:企业强制策略锁死时不让点,避免"能点但必然 400 再弹回"。
|
||||||
|
const methodsLocked = !!(privacy && privacy.editable && privacy.editable.add_me_methods === false);
|
||||||
return <div className="personal-page"><Header title="添加我的方式" back="privacy-settings" /><main className="privacy-list">
|
return <div className="personal-page"><Header title="添加我的方式" back="privacy-settings" /><main className="privacy-list">
|
||||||
<p className="privacy-note">关闭某种方式后,其他人将无法通过该渠道搜索或添加你。</p>
|
<p className="privacy-note">{methodsLocked ? '添加方式由企业统一设置,不可修改。' : '关闭某种方式后,其他人将无法通过该渠道搜索或添加你。'}</p>
|
||||||
{privacy ? <section>{rows.map(row => <div key={row.key}>
|
{privacy ? <section>{rows.map(row => <div key={row.key}>
|
||||||
<span><strong>可通过{row.title}添加我</strong></span>
|
<span><strong>可通过{row.title}添加我</strong></span>
|
||||||
<Toggle checked={!!(privacy.add_me_methods || {})[row.key]} onChange={v => update(row.key, v)} />
|
<Toggle checked={!!(privacy.add_me_methods || {})[row.key]} disabled={methodsLocked} onChange={v => update(row.key, v)} />
|
||||||
</div>)}</section> : null}
|
</div>)}</section> : null}
|
||||||
</main></div>;
|
</main></div>;
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-18
@@ -4692,6 +4692,7 @@ __webpack_require__.r(__webpack_exports__);
|
|||||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||||
/* harmony export */ readCachedData: function() { return /* binding */ readCachedData; },
|
/* harmony export */ readCachedData: function() { return /* binding */ readCachedData; },
|
||||||
/* harmony export */ useCachedData: function() { return /* binding */ useCachedData; },
|
/* harmony export */ useCachedData: function() { return /* binding */ useCachedData; },
|
||||||
|
/* harmony export */ useDebouncedValue: function() { return /* binding */ useDebouncedValue; },
|
||||||
/* harmony export */ writeCachedData: function() { return /* binding */ writeCachedData; }
|
/* harmony export */ writeCachedData: function() { return /* binding */ writeCachedData; }
|
||||||
/* harmony export */ });
|
/* harmony export */ });
|
||||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react");
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react");
|
||||||
@@ -4725,6 +4726,15 @@ function readCachedData(key) {
|
|||||||
function writeCachedData(key, value) {
|
function writeCachedData(key, value) {
|
||||||
if (key) cache.set(key, value);
|
if (key) cache.set(key, value);
|
||||||
}
|
}
|
||||||
|
function useDebouncedValue(value, delay = 300) {
|
||||||
|
const [debounced, setDebounced] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(value);
|
||||||
|
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
||||||
|
if (value === debounced) return undefined;
|
||||||
|
const timer = window.setTimeout(() => setDebounced(value), delay);
|
||||||
|
return () => window.clearTimeout(timer);
|
||||||
|
}, [value, delay, debounced]);
|
||||||
|
return debounced;
|
||||||
|
}
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
@@ -7715,6 +7725,26 @@ function FriendRequests({
|
|||||||
}, void 0, true)]
|
}, void 0, true)]
|
||||||
}, void 0, true);
|
}, void 0, true);
|
||||||
}
|
}
|
||||||
|
const CN_MOBILE = /^1[3-9]\d{9}$/;
|
||||||
|
function classifySearchInput(text) {
|
||||||
|
if (text.startsWith('+')) {
|
||||||
|
return {
|
||||||
|
query: text.replace(/[\s-]/g, ''),
|
||||||
|
type: 'phone'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const digits = text.replace(/[\s-]/g, '');
|
||||||
|
if (CN_MOBILE.test(digits)) {
|
||||||
|
return {
|
||||||
|
query: '+86' + digits,
|
||||||
|
type: 'phone'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
query: text,
|
||||||
|
type: 'exact_account'
|
||||||
|
};
|
||||||
|
}
|
||||||
const GENDER_LABELS = {
|
const GENDER_LABELS = {
|
||||||
male: '男',
|
male: '男',
|
||||||
female: '女',
|
female: '女',
|
||||||
@@ -7739,14 +7769,19 @@ function AddFriend({
|
|||||||
const [searched, setSearched] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(result);
|
const [searched, setSearched] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(result);
|
||||||
const [results, setResults] = react__WEBPACK_IMPORTED_MODULE_0___default().useState([]);
|
const [results, setResults] = react__WEBPACK_IMPORTED_MODULE_0___default().useState([]);
|
||||||
const runSearch = q => {
|
const runSearch = q => {
|
||||||
if (!q.trim()) {
|
const text = q.trim();
|
||||||
|
if (!text) {
|
||||||
setSearched(false);
|
setSearched(false);
|
||||||
setResults([]);
|
setResults([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSearching(true);
|
setSearching(true);
|
||||||
setSearched(true);
|
setSearched(true);
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.searchUsers)(q.trim(), 'exact_account').then(data => setResults(data.items || [])).catch(() => setResults([])).finally(() => setSearching(false));
|
const {
|
||||||
|
query,
|
||||||
|
type
|
||||||
|
} = classifySearchInput(text);
|
||||||
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.searchUsers)(query, type).then(data => setResults(data.items || [])).catch(() => setResults([])).finally(() => setSearching(false));
|
||||||
};
|
};
|
||||||
const shareCard = () => {
|
const shareCard = () => {
|
||||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.createMyQRCode)().then(data => {
|
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.createMyQRCode)().then(data => {
|
||||||
@@ -8917,7 +8952,8 @@ function Favorites({
|
|||||||
const initialType = view === 'favorites-files' ? 'file' : view === 'favorites-text' ? 'text' : view === 'favorites-voice' ? 'voice' : view === 'favorites-links' ? 'link' : 'all';
|
const initialType = view === 'favorites-files' ? 'file' : view === 'favorites-text' ? 'text' : view === 'favorites-voice' ? 'voice' : view === 'favorites-links' ? 'link' : 'all';
|
||||||
const selecting = view === 'favorites-select';
|
const selecting = view === 'favorites-select';
|
||||||
const [type, setType] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(selecting ? 'file' : initialType);
|
const [type, setType] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(selecting ? 'file' : initialType);
|
||||||
const [query, setQuery] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [queryInput, setQueryInput] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
|
const query = (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_6__.useDebouncedValue)(queryInput, 300);
|
||||||
const [selected, setSelected] = react__WEBPACK_IMPORTED_MODULE_0___default().useState([]);
|
const [selected, setSelected] = react__WEBPACK_IMPORTED_MODULE_0___default().useState([]);
|
||||||
const [items, setItems] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(() => (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_6__.readCachedData)('favorites:' + type) || null);
|
const [items, setItems] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(() => (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_6__.readCachedData)('favorites:' + type) || null);
|
||||||
const [cursor, setCursor] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [cursor, setCursor] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
@@ -9004,8 +9040,8 @@ function Favorites({
|
|||||||
right: selecting ? '取消' : '多选',
|
right: selecting ? '取消' : '多选',
|
||||||
onRight: () => navigate(selecting ? 'favorites-files' : 'favorites-select')
|
onRight: () => navigate(selecting ? 'favorites-files' : 'favorites-select')
|
||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(Search, {
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(Search, {
|
||||||
value: query,
|
value: queryInput,
|
||||||
onChange: setQuery,
|
onChange: setQueryInput,
|
||||||
placeholder: "搜索收藏内容"
|
placeholder: "搜索收藏内容"
|
||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("nav", {
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("nav", {
|
||||||
className: "library-type-tabs",
|
className: "library-type-tabs",
|
||||||
@@ -9405,7 +9441,8 @@ function History({
|
|||||||
}) {
|
}) {
|
||||||
const topic = hashParams && hashParams.topic;
|
const topic = hashParams && hashParams.topic;
|
||||||
const cacheKey = topic ? `history:${topic}:${view}` : null;
|
const cacheKey = topic ? `history:${topic}:${view}` : null;
|
||||||
const [query, setQuery] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [queryInput, setQueryInput] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
|
const query = (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_6__.useDebouncedValue)(queryInput, 300);
|
||||||
const [items, setItems] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(() => cacheKey && (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_6__.readCachedData)(cacheKey) || null);
|
const [items, setItems] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(() => cacheKey && (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_6__.readCachedData)(cacheKey) || null);
|
||||||
const [cursor, setCursor] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [cursor, setCursor] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
const [hasMore, setHasMore] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
const [hasMore, setHasMore] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
||||||
@@ -9480,8 +9517,8 @@ function History({
|
|||||||
uid: topic
|
uid: topic
|
||||||
}
|
}
|
||||||
}, void 0, false), view === 'history-search' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(Search, {
|
}, void 0, false), view === 'history-search' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(Search, {
|
||||||
value: query,
|
value: queryInput,
|
||||||
onChange: setQuery,
|
onChange: setQueryInput,
|
||||||
placeholder: "搜索当前会话"
|
placeholder: "搜索当前会话"
|
||||||
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(HistoryNav, {
|
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(HistoryNav, {
|
||||||
active: view
|
active: view
|
||||||
@@ -10573,7 +10610,8 @@ function GroupList({
|
|||||||
joined: false
|
joined: false
|
||||||
});
|
});
|
||||||
const [category, setCategory] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('owned');
|
const [category, setCategory] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('owned');
|
||||||
const [query, setQuery] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [queryInput, setQueryInput] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
|
const query = (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.useDebouncedValue)(queryInput, 300);
|
||||||
const [creating, setCreating] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
const [creating, setCreating] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
||||||
const [name, setName] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [name, setName] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
const [submitting, setSubmitting] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
const [submitting, setSubmitting] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
||||||
@@ -10625,8 +10663,8 @@ function GroupList({
|
|||||||
right: "创建",
|
right: "创建",
|
||||||
onRight: () => setCreating(true)
|
onRight: () => setCreating(true)
|
||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Search, {
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Search, {
|
||||||
value: query,
|
value: queryInput,
|
||||||
onChange: setQuery,
|
onChange: setQueryInput,
|
||||||
placeholder: "搜索群聊"
|
placeholder: "搜索群聊"
|
||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("nav", {
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("nav", {
|
||||||
className: "group-tabs",
|
className: "group-tabs",
|
||||||
@@ -12020,7 +12058,8 @@ function GroupFiles({
|
|||||||
tinode,
|
tinode,
|
||||||
onToast
|
onToast
|
||||||
}) {
|
}) {
|
||||||
const [query, setQuery] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
const [queryInput, setQueryInput] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
|
||||||
|
const query = (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.useDebouncedValue)(queryInput, 300);
|
||||||
const [files, setFiles] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(() => topic && (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.readCachedData)('group-files:' + topic) || null);
|
const [files, setFiles] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(() => topic && (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.readCachedData)('group-files:' + topic) || null);
|
||||||
const [menuOpen, setMenuOpen] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
const [menuOpen, setMenuOpen] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
||||||
const [uploading, setUploading] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(null);
|
const [uploading, setUploading] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(null);
|
||||||
@@ -12147,8 +12186,8 @@ function GroupFiles({
|
|||||||
}, void 0, false), "上传视频"]
|
}, void 0, false), "上传视频"]
|
||||||
}, void 0, true)]
|
}, void 0, true)]
|
||||||
}, void 0, true) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Search, {
|
}, void 0, true) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Search, {
|
||||||
value: query,
|
value: queryInput,
|
||||||
onChange: setQuery,
|
onChange: setQueryInput,
|
||||||
placeholder: "搜索文件名或者上传者"
|
placeholder: "搜索文件名或者上传者"
|
||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("p", {
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("p", {
|
||||||
className: "group-inline-hint",
|
className: "group-inline-hint",
|
||||||
@@ -15312,6 +15351,7 @@ function PrivacySettings({
|
|||||||
if (on.length === 4) return '全部允许';
|
if (on.length === 4) return '全部允许';
|
||||||
return on.length ? on.join('、') : '全部关闭';
|
return on.length ? on.join('、') : '全部关闭';
|
||||||
};
|
};
|
||||||
|
const locked = key => !!(privacy && privacy.editable && privacy.editable[key] === false);
|
||||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
||||||
className: "personal-page",
|
className: "personal-page",
|
||||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Header, {
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Header, {
|
||||||
@@ -15331,10 +15371,11 @@ function PrivacySettings({
|
|||||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("strong", {
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("strong", {
|
||||||
children: "显示消息已读状态"
|
children: "显示消息已读状态"
|
||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("small", {
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("small", {
|
||||||
children: "关闭后你也无法查看对方的已读状态"
|
children: locked('read_receipts') ? '该项由企业统一设置,不可修改' : '关闭后你也无法查看对方的已读状态'
|
||||||
}, void 0, false)]
|
}, void 0, false)]
|
||||||
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Toggle, {
|
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Toggle, {
|
||||||
checked: !!privacy.read_receipts,
|
checked: !!privacy.read_receipts,
|
||||||
|
disabled: locked('read_receipts'),
|
||||||
onChange: v => update('read_receipts', v)
|
onChange: v => update('read_receipts', v)
|
||||||
}, void 0, false)]
|
}, void 0, false)]
|
||||||
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
||||||
@@ -15342,10 +15383,11 @@ function PrivacySettings({
|
|||||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("strong", {
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("strong", {
|
||||||
children: "展示最后在线时间"
|
children: "展示最后在线时间"
|
||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("small", {
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("small", {
|
||||||
children: "仅向联系人显示"
|
children: locked('last_seen_visibility') ? '该项由企业统一设置,不可修改' : '仅向联系人显示'
|
||||||
}, void 0, false)]
|
}, void 0, false)]
|
||||||
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Toggle, {
|
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Toggle, {
|
||||||
checked: privacy.last_seen_visibility !== 'nobody',
|
checked: privacy.last_seen_visibility !== 'nobody',
|
||||||
|
disabled: locked('last_seen_visibility'),
|
||||||
onChange: v => update('last_seen_visibility', v ? 'friends' : 'nobody')
|
onChange: v => update('last_seen_visibility', v ? 'friends' : 'nobody')
|
||||||
}, void 0, false)]
|
}, void 0, false)]
|
||||||
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
||||||
@@ -15353,10 +15395,11 @@ function PrivacySettings({
|
|||||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("strong", {
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("strong", {
|
||||||
children: "允许好友查看我的资料"
|
children: "允许好友查看我的资料"
|
||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("small", {
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("small", {
|
||||||
children: "关闭后仅你自己可见"
|
children: locked('profile_visibility') ? '该项由企业统一设置,不可修改' : '关闭后仅你自己可见'
|
||||||
}, void 0, false)]
|
}, void 0, false)]
|
||||||
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Toggle, {
|
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Toggle, {
|
||||||
checked: privacy.profile_visibility !== 'nobody',
|
checked: privacy.profile_visibility !== 'nobody',
|
||||||
|
disabled: locked('profile_visibility'),
|
||||||
onChange: v => update('profile_visibility', v ? 'friends' : 'nobody')
|
onChange: v => update('profile_visibility', v ? 'friends' : 'nobody')
|
||||||
}, void 0, false)]
|
}, void 0, false)]
|
||||||
}, void 0, true)]
|
}, void 0, true)]
|
||||||
@@ -15409,6 +15452,7 @@ function FriendDiscoverySettings({
|
|||||||
key: 'group',
|
key: 'group',
|
||||||
title: '群聊'
|
title: '群聊'
|
||||||
}];
|
}];
|
||||||
|
const methodsLocked = !!(privacy && privacy.editable && privacy.editable.add_me_methods === false);
|
||||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
||||||
className: "personal-page",
|
className: "personal-page",
|
||||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Header, {
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Header, {
|
||||||
@@ -15418,7 +15462,7 @@ function FriendDiscoverySettings({
|
|||||||
className: "privacy-list",
|
className: "privacy-list",
|
||||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("p", {
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("p", {
|
||||||
className: "privacy-note",
|
className: "privacy-note",
|
||||||
children: "关闭某种方式后,其他人将无法通过该渠道搜索或添加你。"
|
children: methodsLocked ? '添加方式由企业统一设置,不可修改。' : '关闭某种方式后,其他人将无法通过该渠道搜索或添加你。'
|
||||||
}, void 0, false), privacy ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("section", {
|
}, void 0, false), privacy ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("section", {
|
||||||
children: rows.map(row => (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
children: rows.map(row => (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
|
||||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("span", {
|
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("span", {
|
||||||
@@ -15427,6 +15471,7 @@ function FriendDiscoverySettings({
|
|||||||
}, void 0, true)
|
}, void 0, true)
|
||||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Toggle, {
|
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Toggle, {
|
||||||
checked: !!(privacy.add_me_methods || {})[row.key],
|
checked: !!(privacy.add_me_methods || {})[row.key],
|
||||||
|
disabled: methodsLocked,
|
||||||
onChange: v => update(row.key, v)
|
onChange: v => update(row.key, v)
|
||||||
}, void 0, false)]
|
}, void 0, false)]
|
||||||
}, row.key, true))
|
}, row.key, true))
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user