Compare commits

...

2 Commits

Author SHA1 Message Date
kuaifan
0b0c2f7cc6 feat: 添加获取会员拓展信息功能,优化用户信息加载 2025-11-05 21:23:32 +08:00
kuaifan
e096707c44 feat: 优化个性标签 2025-11-05 20:46:17 +08:00
9 changed files with 416 additions and 122 deletions

View File

@ -388,9 +388,6 @@ class UsersController extends AbstractController
$data['nickname_original'] = $user->getRawOriginal('nickname'); $data['nickname_original'] = $user->getRawOriginal('nickname');
$data['department_name'] = $user->getDepartmentName(); $data['department_name'] = $user->getDepartmentName();
$data['department_owner'] = UserDepartment::where('parent_id',0)->where('owner_userid', $user->userid)->exists(); // 适用默认部门下第1级负责人才能添加部门OKR $data['department_owner'] = UserDepartment::where('parent_id',0)->where('owner_userid', $user->userid)->exists(); // 适用默认部门下第1级负责人才能添加部门OKR
$tagMeta = UserTag::listWithMeta($user->userid, $user);
$data['personal_tags'] = $tagMeta['top'];
$data['personal_tags_total'] = $tagMeta['total'];
return Base::retSuccess('success', $data); return Base::retSuccess('success', $data);
} }
@ -832,16 +829,76 @@ class UsersController extends AbstractController
$basic = UserDelete::userid2basic($id); $basic = UserDelete::userid2basic($id);
} }
if ($basic) { if ($basic) {
$tagMeta = UserTag::listWithMeta($basic->userid, $viewer);
$basic->personal_tags = $tagMeta['top'];
$basic->personal_tags_total = $tagMeta['total'];
//
$retArray[] = $basic; $retArray[] = $basic;
} }
} }
return Base::retSuccess('success', $retArray); return Base::retSuccess('success', $retArray);
} }
/**
* @api {get} api/users/extra 获取会员拓展信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName extra
*
* @apiParam {Number|Number[]} [userid] 会员ID多个格式jsonArray一次最多50个不传默认为当前登录会员
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object[]} data 返回数据
*/
public function extra()
{
$sharekey = Request::header('sharekey');
$shareInfo = $sharekey ? Meeting::getShareInfo($sharekey) : null;
$viewer = null;
if (empty($shareInfo)) {
$viewer = User::auth();
} elseif (Doo::userId() > 0) {
$viewer = User::whereUserid(Doo::userId())->first();
}
//
$userid = Request::input('userid');
$array = Base::json2array($userid);
if (empty($array) && $userid) {
$array[] = $userid;
}
if (empty($array) && $viewer) {
$array[] = $viewer->userid;
}
if (empty($array)) {
return Base::retError('参数错误');
}
if (count($array) > 50) {
return Base::retError('一次最多只能获取50条数据');
}
//
$userids = array_values(array_unique(array_filter(array_map(function ($id) {
$id = intval($id);
return $id > 0 ? $id : null;
}, $array))));
if (empty($userids)) {
return Base::retError('参数错误');
}
//
$fields = ['userid', 'birthday', 'address', 'introduction'];
$retArray = [];
foreach ($userids as $id) {
$user = User::whereUserid($id)->select($fields)->first();
if (empty($user)) {
continue;
}
$extended = Arr::only($user->toArray(), $fields);
$tagMeta = UserTag::listWithMeta($id, $viewer);
$extended['personal_tags'] = $tagMeta['top'];
$extended['personal_tags_total'] = $tagMeta['total'];
$retArray[] = $extended;
}
return Base::retSuccess('success', $retArray);
}
/** /**
* @api {get} api/users/lists 会员列表(限管理员) * @api {get} api/users/lists 会员列表(限管理员)
* *

View File

@ -92,7 +92,7 @@ class User extends AbstractModel
public static $defaultAvatarMode = 'auto'; public static $defaultAvatarMode = 'auto';
// 基本信息的字段 // 基本信息的字段
public static $basicField = ['userid', 'email', 'nickname', 'profession', 'birthday', 'address', 'introduction', 'department', 'userimg', 'bot', 'az', 'pinyin', 'line_at', 'disable_at']; public static $basicField = ['userid', 'email', 'nickname', 'profession', 'department', 'userimg', 'bot', 'az', 'pinyin', 'line_at', 'disable_at'];
/** /**
* 昵称 * 昵称

View File

@ -5,7 +5,7 @@
:fullscreen="isFullscreen" :fullscreen="isFullscreen"
:mask-closable="false" :mask-closable="false"
:footer-hide="true" :footer-hide="true"
width="420" width="480"
> >
<div class="user-detail-body"> <div class="user-detail-body">
<div class="profile-header"> <div class="profile-header">
@ -13,7 +13,7 @@
<div class="profile-avatar"> <div class="profile-avatar">
<UserAvatar <UserAvatar
:userid="userData.userid" :userid="userData.userid"
:size="80" :size="96"
:show-state-dot="false" :show-state-dot="false"
@on-click="onOpenAvatar" @on-click="onOpenAvatar"
/> />
@ -21,46 +21,22 @@
</div> </div>
<div class="profile-content"> <div class="profile-content">
<div class="user-info-top"> <div class="user-info-top">
<span class="username" <h1 class="username">
>@{{ userData.profession || "管理员" }}</span {{ userData.nickname }}
>
<h1 class="fullname">
{{ userData.nickname}}
</h1> </h1>
<div class="meta"> <div class="meta">
<!-- <span>{{userData.address || 'Bandung'}}</span> --> <span @click="commonDialogShow = true" class="common-dialog">{{ $L(userId == userData.userid ? "我的群组" : "共同群组") }}:<em>{{ $L("(*)个", commonDialog.total) }}</em></span>
<span @click="commonDialogShow = true"
class="common-dialog"
>{{ $L("共同群组") }}:
{{ $L("(*)个", commonDialog.total) }}</span
>
<span class="separator">|</span> <span class="separator">|</span>
<span <span>{{ $L("最后在线") }}: {{$A.newDateString( userData.line_at, "YYYY-MM-DD HH:mm") || "-"}}</span>
>{{ $L("最后在线") }}:
{{
$A.newDateString(
userData.line_at,
"YYYY-MM-DD HH:mm"
) || "-"
}}</span
>
</div> </div>
</div> </div>
<div class="profile-actions"> <div class="profile-actions">
<Button <Button @click="onOpenDialog"><i class="taskfont">&#xe6eb;</i>{{ $L("开始聊天") }}</Button>
icon="md-chatbubbles" <Button @click="onCreateGroup"><i class="taskfont">&#xe63f;</i>{{ $L("创建群组") }}</Button>
@click="onOpenDialog"
>{{ $L("开始聊天") }}</Button
>
<Button
icon="md-people"
@click="onCreateGroup"
>{{ $L("创建群组") }}</Button
>
</div> </div>
<div class="profile-bio"> <div v-if="userData.introduction" class="profile-bio">
<p>{{ userData.introduction }}</p> <p>{{ userData.introduction }}</p>
</div> </div>
@ -68,57 +44,57 @@
<h2>{{ $L("个人信息") }}</h2> <h2>{{ $L("个人信息") }}</h2>
<ul> <ul>
<li> <li>
<Icon type="ios-person-outline" /> <Icon type="ios-briefcase-outline" />
<span class="label">{{ $L("职位/职称") }}</span>
<span class="value">{{userData.profession || "-"}}</span>
</li>
<li>
<Icon type="ios-people-outline" />
<span class="label">{{ $L("部门") }}</span> <span class="label">{{ $L("部门") }}</span>
<span class="value">{{ <span class="value">{{userData.department_name || "-"}}</span>
userData.department_name || "-"
}}</span>
</li> </li>
<li> <li>
<Icon type="ios-mail-outline" /> <Icon type="ios-mail-outline" />
<span class="label">{{ $L("邮箱") }}</span> <span class="label">{{ $L("邮箱") }}</span>
<span class="value">{{ <span @click="onOpenEmail" class="value" :class="{ 'clickable': userData.email }">{{userData.email || "-"}}</span>
userData.email || "-"
}}</span>
</li> </li>
<li> <li>
<Icon type="ios-call-outline" /> <Icon type="ios-call-outline" />
<span class="label">{{ $L("电话") }}</span> <span class="label">{{ $L("电话") }}</span>
<span class="value">{{ userData.tel || "-" }}</span> <span @click="onOpenTel" class="value" :class="{ 'clickable': userData.tel }">{{ userData.tel || "-" }}</span>
</li> </li>
<li> <li v-if="userData.birthday">
<Icon type="ios-calendar-outline" /> <Icon type="ios-calendar-outline" />
<span class="label">{{ $L("生日") }}</span> <span class="label">{{ $L("生日") }}</span>
<span class="value">{{ <span class="value">{{userData.birthday || "-"}}</span>
userData.birthday || "-"
}}</span>
</li> </li>
</ul> </ul>
<div class="profile-tags" @click.capture="onOpenTagsModal"> <div class="profile-tags">
<div v-if="displayTags.length" class="tags-list"> <div v-if="displayTags.length" class="tags-list">
<Button
type="dashed"
class="manage-tags-btn icon"
@click.stop="onOpenTagsModal"
>
<Icon type="ios-settings-outline" /> 管理
</Button>
<Button <Button
v-for="tag in displayTags" v-for="tag in displayTags"
:key="tag.id" :key="tag.id"
:type="tag.recognized ? 'primary' : 'default'" :type="tag.recognized ? 'primary' : 'default'"
@click="onOpenTagsModal"
> >
{{ tag.name }} {{ tag.name }}
<span v-if="tag.recognition_total > 0" class="recognition-total">{{tag.recognition_total}}</span>
</Button>
<Button
type="dashed"
class="manage-tags-btn icon"
@click="onOpenTagsModal"
>
<Icon type="ios-settings-outline" /> 管理
</Button> </Button>
</div> </div>
<div v-else class="tags-empty"> <div v-else class="tags-empty">
<Button <Button
type="dashed" type="dashed"
size="small"
icon="md-add" icon="md-add"
class="add-tag-btn" class="add-tag-btn"
@click.stop="onOpenTagsModal" @click="onOpenTagsModal"
>{{ $L("添加标签") }}</Button >{{ $L("添加标签") }}</Button
> >
</div> </div>
@ -160,6 +136,7 @@ export default {
userid: 0, userid: 0,
}, },
showModal: false, showModal: false,
extraLoading: 0,
tagModalVisible: false, tagModalVisible: false,
commonDialog: { commonDialog: {
userid: null, userid: null,
@ -227,6 +204,7 @@ export default {
.then((user) => { .then((user) => {
this.userData = user; this.userData = user;
this.ensureTagDefaults(); this.ensureTagDefaults();
this.loadUserExtra(userid);
this.showModal = true; this.showModal = true;
this.loadCommonDialogCount(); this.loadCommonDialogCount();
}) })
@ -407,11 +385,69 @@ export default {
$A.modalError(msg); $A.modalError(msg);
}); });
}, },
onOpenEmail() {
if (!this.userData.email) {
return;
}
$A.modalConfirm({
content: `是否发送邮件给 ${this.userData.nickname}`,
onOk: () => {
window.open(`mailto:${this.userData.email}`);
}
});
},
onOpenTel() {
if (!this.userData.tel) {
return;
}
$A.modalConfirm({
content: `是否拨打电话给 ${this.userData.nickname}`,
onOk: () => {
if ($A.isEEUIApp()) {
$A.eeuiAppSendMessage({
action: 'callTel',
tel: this.userData.tel
});
} else {
window.open(`tel:${this.userData.tel}`);
}
}
});
},
loadUserExtra(userid) {
const targetId = Number(userid);
if (!targetId) {
return;
}
this.extraLoading += 1;
this.$store
.dispatch("getUserExtra", {
userid: targetId,
checkAuth: false,
})
.then((extra) => {
if (extra) {
this.applyExtraData(extra, targetId);
}
})
.catch((error) => {
console.warn(error);
})
.finally(() => {
this.extraLoading = Math.max(0, this.extraLoading - 1);
});
},
applyExtraData(extra, targetId) {
if (!extra || this.userData.userid !== targetId) {
return;
}
this.userData = Object.assign({}, this.userData, extra);
this.ensureTagDefaults();
},
}, },
}; };
</script> </script>
<style lang="scss" scoped>
// The styles will be moved to the SCSS file as requested.
// This scoped style block can be removed if not needed for specific overrides.
</style>

View File

@ -2,17 +2,12 @@
<ModalAlive <ModalAlive
v-model="visible" v-model="visible"
class-name="user-tags-manage-modal" class-name="user-tags-manage-modal"
:title="$L('个性标签管理')"
:mask-closable="false" :mask-closable="false"
:footer-hide="true" :footer-hide="true"
width="520" width="520"
:closable="true"> :closable="true">
<div class="tag-modal-container"> <div class="tag-modal-container">
<div class="tag-modal-header">
<h3>{{$L('个性标签管理')}}</h3>
<p class="tag-modal-meta">
<span>{{$L('当前共(*)个标签', total)}}</span>
</p>
</div>
<div class="tag-modal-form"> <div class="tag-modal-form">
<Input <Input
v-model="newTagName" v-model="newTagName"
@ -43,7 +38,7 @@
:class="{'is-editing': editId === tag.id}"> :class="{'is-editing': editId === tag.id}">
<div class="tag-item-main"> <div class="tag-item-main">
<div class="tag-name" v-if="editId !== tag.id"> <div class="tag-name" v-if="editId !== tag.id">
<Tag :color="tag.recognized ? '#84C56A' : 'default'" class="tag-pill">{{tag.name}}</Tag> <div class="tag-pill" :class="{'is-recognized': tag.recognized}">{{tag.name}}</div>
</div> </div>
<div class="tag-name edit" v-else> <div class="tag-name edit" v-else>
<Input <Input
@ -62,7 +57,7 @@
:loading="isPending(tag.id, 'recognize')" :loading="isPending(tag.id, 'recognize')"
@click="toggleRecognize(tag)"> @click="toggleRecognize(tag)">
<Icon type="md-thumbs-up" /> <Icon type="md-thumbs-up" />
<span>{{tag.recognition_total}}</span> <span v-if="tag.recognition_total > 0">{{tag.recognition_total}}</span>
<span class="recognize-text">{{$L('认可')}}</span> <span class="recognize-text">{{$L('认可')}}</span>
</Button> </Button>
<template v-if="editId === tag.id"> <template v-if="editId === tag.id">
@ -92,11 +87,14 @@
</div> </div>
</div> </div>
<div class="tag-meta-info" v-if="tag.created_by_name"> <div class="tag-meta-info" v-if="tag.created_by_name">
<span>{{$L('由(*)创建', tag.created_by_name)}}</span> <span>{{$L('由 (*) 创建', tag.created_by_name)}}</span>
</div> </div>
</li> </li>
</ul> </ul>
</div> </div>
<div v-if="total > 0" class="tag-modal-footer">
<span>{{$L('当前共(*)个标签', total)}}</span>
</div>
</div> </div>
</ModalAlive> </ModalAlive>
</template> </template>
@ -364,23 +362,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.user-tags-manage-modal { .user-tags-manage-modal {
.tag-modal-container { .tag-modal-container {
padding: 16px 20px 12px; padding-bottom: 20px;
}
.tag-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.tag-modal-meta {
margin: 0;
color: #909399;
font-size: 12px;
}
} }
.tag-modal-form { .tag-modal-form {
margin-bottom: 16px; margin-bottom: 16px;
@ -388,6 +370,7 @@ export default {
.tag-modal-body { .tag-modal-body {
max-height: 360px; max-height: 360px;
overflow-y: auto; overflow-y: auto;
margin-bottom: 16px;
} }
.tag-loading { .tag-loading {
display: flex; display: flex;
@ -396,7 +379,7 @@ export default {
} }
.tag-empty { .tag-empty {
text-align: center; text-align: center;
padding: 32px 0; padding: 36px 0 32px;
color: #909399; color: #909399;
p { p {
margin-top: 8px; margin-top: 8px;
@ -432,7 +415,16 @@ export default {
} }
} }
.tag-pill { .tag-pill {
cursor: default; padding: 6px 12px;
border-radius: 12px;
font-size: 13px;
line-height: 1;
user-select: none;
background-color: #f5f5f5;
color: #606266;
&.is-recognized {
color: #67c23a;
}
} }
.tag-actions { .tag-actions {
display: flex; display: flex;
@ -442,9 +434,12 @@ export default {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
.ivu-icon {
transform: translateY(-1px);
}
.recognize-text { .recognize-text {
padding-left: 4px;
font-size: 12px; font-size: 12px;
color: #606266;
} }
} }
} }
@ -455,5 +450,9 @@ export default {
} }
} }
} }
.tag-modal-footer {
color: #909399;
font-size: 12px;
}
} }
</style> </style>

View File

@ -45,13 +45,17 @@
:placeholder="$L('请输入个人简介')"></Input> :placeholder="$L('请输入个人简介')"></Input>
</FormItem> </FormItem>
<FormItem :label="$L('个性标签')"> <FormItem :label="$L('个性标签')">
<div class="user-tags-preview" @click="openTagModal"> <div class="user-tags-preview">
<template v-if="displayTags.length"> <template v-if="displayTags.length">
<Tag <div
v-for="tag in displayTags" v-for="tag in displayTags"
:key="tag.id" :key="tag.id"
:color="tag.recognized ? '#84C56A' : 'default'" class="tag-pill"
class="tag-pill">{{tag.name}}</Tag> :class="{'is-recognized': tag.recognized}"
@click="openTagModal">
{{tag.name}}
<span v-if="tag.recognition_total > 0">{{tag.recognition_total}}</span>
</div>
</template> </template>
<span v-else class="tags-empty">{{$L('暂无个性标签')}}</span> <span v-else class="tags-empty">{{$L('暂无个性标签')}}</span>
<span v-if="personalTagTotal > displayTags.length" class="tags-total">{{$L('(*)', personalTagTotal)}}</span> <span v-if="personalTagTotal > displayTags.length" class="tags-total">{{$L('(*)', personalTagTotal)}}</span>
@ -112,10 +116,13 @@ export default {
tagModalVisible: false, tagModalVisible: false,
personalTags: [], personalTags: [],
personalTagTotal: 0, personalTagTotal: 0,
extraLoading: 0,
extraLoaded: false,
} }
}, },
mounted() { mounted() {
this.initData(); this.initData();
this.fetchExtraInfo();
}, },
computed: { computed: {
...mapState(['userInfo', 'formOptions']), ...mapState(['userInfo', 'formOptions']),
@ -127,6 +134,8 @@ export default {
watch: { watch: {
userInfo() { userInfo() {
this.initData(); this.initData();
this.extraLoaded = false;
this.fetchExtraInfo();
} }
}, },
methods: { methods: {
@ -143,11 +152,12 @@ export default {
this.syncPersonalTags(); this.syncPersonalTags();
}, },
syncPersonalTags() { syncPersonalTags(source = null) {
const tags = Array.isArray(this.userInfo.personal_tags) ? this.userInfo.personal_tags : []; const target = source || this.userInfo;
const tags = Array.isArray(target.personal_tags) ? target.personal_tags : [];
this.personalTags = tags.slice(0, 10); this.personalTags = tags.slice(0, 10);
this.personalTagTotal = typeof this.userInfo.personal_tags_total === 'number' this.personalTagTotal = typeof target.personal_tags_total === 'number'
? this.userInfo.personal_tags_total ? target.personal_tags_total
: this.personalTags.length; : this.personalTags.length;
}, },
@ -186,6 +196,39 @@ export default {
onTagsUpdated({top, total}) { onTagsUpdated({top, total}) {
this.personalTags = Array.isArray(top) ? top : []; this.personalTags = Array.isArray(top) ? top : [];
this.personalTagTotal = typeof total === 'number' ? total : this.personalTags.length; this.personalTagTotal = typeof total === 'number' ? total : this.personalTags.length;
},
fetchExtraInfo() {
const userid = $A.runNum(this.userInfo.userid);
if (userid <= 0) {
return;
}
if (this.extraLoaded || this.extraLoading > 0) {
return;
}
this.extraLoading++;
this.$store
.dispatch("getUserExtra", {userid})
.then((extra) => {
if (extra) {
this.applyExtraInfo(extra);
this.extraLoaded = true;
}
})
.catch((error) => {
console.warn(error);
})
.finally(() => {
this.extraLoading = Math.max(0, this.extraLoading - 1);
});
},
applyExtraInfo(extra) {
this.$set(this.formData, 'birthday', extra?.birthday || '');
this.$set(this.formData, 'address', extra?.address || '');
this.$set(this.formData, 'introduction', extra?.introduction || '');
this.syncPersonalTags(extra);
this.formData_bak = $A.cloneJSON(this.formData);
} }
} }
} }
@ -198,10 +241,34 @@ export default {
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 8px;
min-height: 32px; min-height: 32px;
cursor: pointer;
.tag-pill { .tag-pill {
cursor: pointer; cursor: pointer;
padding: 5px 12px;
border-radius: 12px;
font-size: 13px;
line-height: 1;
user-select: none;
background-color: #f5f5f5;
color: #606266;
&.is-recognized {
color: #67c23a;
}
span {
padding-left: 8px;
position: relative;
&:before {
content: '';
position: absolute;
left: 2px;
top: 50%;
transform: translateY(-50%);
width: 2px;
height: 2px;
border-radius: 50%;
background-color: currentColor;
}
}
} }
.tags-empty { .tags-empty {

View File

@ -863,6 +863,71 @@ export default {
}) })
}, },
/**
* 获取会员拓展信息生日地址简介标签等
* @param state
* @param dispatch
* @param commit
* @param payload {userid|{userid, force, checkAuth}}
* @returns {Promise<unknown>}
*/
getUserExtra({state, dispatch, commit}, payload) {
return new Promise(async (resolve, reject) => {
const options = (payload && typeof payload === 'object' && !Array.isArray(payload))
? payload
: {userid: payload};
let {userid, force = false, checkAuth = false} = options;
const original = userid;
const ids = (Array.isArray(userid) ? userid : [userid])
.map(id => Number(id))
.filter(id => Number.isFinite(id) && id > 0);
if (ids.length === 0) {
resolve(Array.isArray(original) ? [] : null);
return;
}
const cachedBefore = ids
.map(id => state.userExtraCache[String(id)])
.filter(item => typeof item === 'object' && item !== null);
const missing = ids.filter(id => force || !state.userExtraCache[String(id)]);
if (missing.length > 0) {
try {
const {data} = await dispatch("call", {
url: 'users/extra',
data: {userid: missing},
checkAuth
});
const extraList = Array.isArray(data) ? data : [];
extraList.forEach(item => {
const id = Number(item?.userid);
if (!Number.isFinite(id) || id <= 0) {
return;
}
const normalized = Object.assign({}, item, {userid: id});
commit('user/extra/save', {userid: id, data: normalized});
const existing = state.cacheUserBasic.find(entry => entry.userid == id);
if (existing) {
dispatch("saveUserBasic", Object.assign({}, existing, normalized));
}
});
} catch (error) {
console.warn(error);
if (cachedBefore.length === 0) {
reject(error);
return;
}
}
}
const results = ids
.map(id => state.userExtraCache[String(id)])
.filter(item => typeof item === 'object' && item !== null);
if (Array.isArray(original)) {
resolve(results);
} else {
resolve(results[0] || null);
}
});
},
/** /**
* 保存用户基础信息 * 保存用户基础信息
* @param commit * @param commit
@ -1099,6 +1164,7 @@ export default {
// Reset auth exception flag after successful login flow // Reset auth exception flag after successful login flow
state.ajaxAuthException = null state.ajaxAuthException = null
state.userExtraCache = {}
resolve() resolve()
}); });

View File

@ -30,6 +30,27 @@ export default {
$A.IDBSave("cacheUserBasic", state.cacheUserBasic, 600) $A.IDBSave("cacheUserBasic", state.cacheUserBasic, 600)
}, },
'user/extra/save': function(state, {userid, data}) {
const id = Number(userid);
if (!Number.isFinite(id) || id <= 0 || typeof data !== 'object' || data === null) {
return;
}
const key = String(id);
const cache = Object.assign({}, state.userExtraCache);
cache[key] = Object.assign({}, data, {userid: id});
state.userExtraCache = cache;
},
'user/extra/clear': function(state, userid) {
if (typeof userid === 'number' || typeof userid === 'string') {
const cache = Object.assign({}, state.userExtraCache);
delete cache[String(userid)];
state.userExtraCache = cache;
} else {
state.userExtraCache = {};
}
},
// 共同群聊 // 共同群聊
'common/dialog/count/save': function(state, {userid, total, updatedAt = Date.now()}) { 'common/dialog/count/save': function(state, {userid, total, updatedAt = Date.now()}) {
if (!userid) { if (!userid) {

View File

@ -90,6 +90,7 @@ export default {
// User // User
cacheUserWait: [], cacheUserWait: [],
cacheUserBasic: [], cacheUserBasic: [],
userExtraCache: {},
// 日历 // 日历
cacheCalendarView: null, cacheCalendarView: null,

View File

@ -1,18 +1,30 @@
.common-user-detail-modal { .common-user-detail-modal {
.ivu-modal-fullscreen {
.ivu-modal-body {
border-radius: 16px 16px 0 0;
}
}
.ivu-modal-content { .ivu-modal-content {
border-radius: 16px !important; background-color: transparent;
margin-top: calc(var(--status-bar-height) + 46px) !important; margin-top: calc(var(--status-bar-height) + 46px) !important;
} }
.ivu-modal-close {
.ivu-icon-ios-close {
color: #ffffff;
}
}
.ivu-modal-body { .ivu-modal-body {
padding: 0 !important; padding: 0 !important;
background-color: #ffffff;
border-radius: 16px;
} }
.user-detail-body { .user-detail-body {
.profile-header { .profile-header {
position: relative; position: relative;
height: 160px; height: 180px;
.cover-photo { .cover-photo {
background: $primary-color; background: $primary-color;
height: 120px; height: 130px;
border-top-left-radius: 16px; border-top-left-radius: 16px;
border-top-right-radius: 16px; border-top-right-radius: 16px;
} }
@ -34,45 +46,59 @@
.user-info-top { .user-info-top {
.username { .username {
color: $primary-desc-color;
font-size: 14px;
}
.fullname {
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;
margin: 4px 0;
} }
.meta { .meta {
margin-top: 8px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
color: #808695; color: #808695;
font-size: 12px; font-size: 13px;
.common-dialog { .common-dialog {
cursor: pointer; cursor: pointer;
em {
padding-left: 4px;
font-style: normal;
color: $primary-color;
}
} }
.separator { .separator {
margin: 0 8px; margin: 0 8px;
opacity: 0.3;
} }
} }
} }
.profile-actions { .profile-actions {
margin: 16px 0; margin-top: 16px;
display: flex; display: flex;
justify-content: center; justify-content: center;
gap: 8px; gap: 12px;
.ivu-btn {
border-radius: 6px;
border-color: #ececec;
&:hover {
border-color: #a2d98d;
}
.taskfont {
margin-right: 6px;
}
}
} }
.profile-bio { .profile-bio {
color: $primary-title-color; color: $primary-title-color;
line-height: 1.5; line-height: 1.6;
margin: 16px 0; margin-top: 16px;
padding: 0 50px; padding: 0 50px;
font-size: 12px; font-size: 12px;
opacity: 0.8;
} }
.profile-information { .profile-information {
margin-top: 24px;
text-align: left; text-align: left;
background-color: #f8f8f9; background-color: #f8f8f9;
padding: 16px; padding: 16px;
@ -90,7 +116,7 @@
li { li {
display: flex; display: flex;
align-items: flex-start; // 避免多行内容导致垂直居中不齐 align-items: flex-start;
padding: 8px 0; padding: 8px 0;
font-size: 14px; font-size: 14px;
@ -118,6 +144,9 @@
white-space: normal; white-space: normal;
word-break: break-word; word-break: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
&.clickable {
cursor: pointer;
}
} }
} }
} }
@ -126,14 +155,32 @@
.profile-tags { .profile-tags {
margin-top: 12px; margin-top: 12px;
padding-top: 12px; padding-top: 12px;
border-top: 1px solid #80869550; border-top: 1px solid #e5e5e5;
cursor: pointer; display: flex;
.tags-list { .tags-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 8px;
justify-content: center; justify-content: center;
position: relative; position: relative;
.recognition-total {
padding-left: 8px;
position: relative;
&:before {
content: '';
position: absolute;
left: 2px;
top: 50%;
transform: translateY(-50%);
width: 2px;
height: 2px;
border-radius: 50%;
background-color: currentColor;
}
}
}
.ivu-btn {
border-radius: 6px;
} }
.ivu-tag { .ivu-tag {
margin: 0; margin: 0;