104 lines
3.4 KiB
Vue
104 lines
3.4 KiB
Vue
<template>
|
||||
|
|
<aside class="conversation-list">
|
|||
|
|
<header class="list-header">
|
|||
|
|
<div>
|
|||
|
|
<h2>联系人</h2>
|
|||
|
|
<p>{{ contacts.contacts.length }} 位好友</p>
|
|||
|
|
</div>
|
|||
|
|
<div class="header-actions">
|
|||
|
|
<button class="icon-button" title="添加联系人" @click="openAddContact">+</button>
|
|||
|
|
<button class="icon-button" title="刷新" @click="contacts.refreshContacts({ force: true })">↻</button>
|
|||
|
|
</div>
|
|||
|
|
</header>
|
|||
|
|
|
|||
|
|
<div class="search-box">
|
|||
|
|
<input v-model.trim="keyword" placeholder="搜索联系人">
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="sidebar-scroll">
|
|||
|
|
<button
|
|||
|
|
v-for="item in filteredContacts"
|
|||
|
|
:key="item.id"
|
|||
|
|
class="conversation-item"
|
|||
|
|
:class="{ active: item.id === contacts.activeId }"
|
|||
|
|
@click="contacts.setActiveContact(item.id)"
|
|||
|
|
>
|
|||
|
|
<img v-if="item.portrait" class="conversation-avatar image-avatar" :src="item.portrait" alt="">
|
|||
|
|
<span v-else class="conversation-avatar">{{ item.avatarText }}</span>
|
|||
|
|
<span class="conversation-copy">
|
|||
|
|
<strong>{{ item.title }}</strong>
|
|||
|
|
<small>{{ item.subtitle }}</small>
|
|||
|
|
</span>
|
|||
|
|
</button>
|
|||
|
|
|
|||
|
|
<div v-if="!filteredContacts.length" class="empty-list">
|
|||
|
|
<strong>{{ contacts.loading ? '正在加载' : '暂无联系人' }}</strong>
|
|||
|
|
<span>{{ contacts.loading ? '请稍候' : '好友同步完成后会显示在这里' }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div v-if="addingContact" class="modal-backdrop" @click.self="closeAddContact">
|
|||
|
|
<form class="modal-panel" @submit.prevent="submitAddContact">
|
|||
|
|
<header>
|
|||
|
|
<strong>添加联系人</strong>
|
|||
|
|
<button type="button" class="icon-button" title="关闭" @click="closeAddContact">×</button>
|
|||
|
|
</header>
|
|||
|
|
<label>
|
|||
|
|
<span>用户ID / 手机号 / 用户名</span>
|
|||
|
|
<input v-model.trim="addKeyword" autocomplete="off" placeholder="请输入要添加的联系人">
|
|||
|
|
</label>
|
|||
|
|
<p v-if="contacts.actionError" class="form-error">{{ contacts.actionError }}</p>
|
|||
|
|
<p v-if="contacts.actionInfo" class="form-info">{{ contacts.actionInfo }}</p>
|
|||
|
|
<div class="modal-actions">
|
|||
|
|
<button type="button" class="ghost-inline-action" @click="closeAddContact">取消</button>
|
|||
|
|
<button class="primary-inline-action" :disabled="contacts.loading || !addKeyword">
|
|||
|
|
{{ contacts.loading ? '添加中...' : '添加' }}
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</form>
|
|||
|
|
</div>
|
|||
|
|
</aside>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { computed, ref } from 'vue';
|
|||
|
|
import { useContactStore } from '@/stores/contacts';
|
|||
|
|
|
|||
|
|
const contacts = useContactStore();
|
|||
|
|
const keyword = ref('');
|
|||
|
|
const addingContact = ref(false);
|
|||
|
|
const addKeyword = ref('');
|
|||
|
|
|
|||
|
|
const filteredContacts = computed(() => {
|
|||
|
|
const value = keyword.value.toLowerCase();
|
|||
|
|
if (!value) {
|
|||
|
|
return contacts.contacts;
|
|||
|
|
}
|
|||
|
|
return contacts.contacts.filter(item => {
|
|||
|
|
return item.title.toLowerCase().includes(value)
|
|||
|
|
|| item.subtitle.toLowerCase().includes(value)
|
|||
|
|
|| item.uid.toLowerCase().includes(value);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
function openAddContact() {
|
|||
|
|
contacts.actionError = '';
|
|||
|
|
contacts.actionInfo = '';
|
|||
|
|
addKeyword.value = '';
|
|||
|
|
addingContact.value = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function closeAddContact() {
|
|||
|
|
addingContact.value = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function submitAddContact() {
|
|||
|
|
const added = await contacts.addContact(addKeyword.value);
|
|||
|
|
if (added) {
|
|||
|
|
window.setTimeout(() => {
|
|||
|
|
addingContact.value = false;
|
|||
|
|
}, 500);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|