feat: Conversations封装、首页开发
This commit is contained in:
173
src/components/xt-chat/conversations/index.vue
Normal file
173
src/components/xt-chat/conversations/index.vue
Normal file
@ -0,0 +1,173 @@
|
||||
<script lang="tsx">
|
||||
import { ref, defineComponent, computed } from 'vue';
|
||||
import { Input, Dropdown, Menu } from 'ant-design-vue';
|
||||
import type { MenuProps } from 'ant-design-vue';
|
||||
|
||||
import type { VNode } from 'vue';
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import TextoverTips from '@/components/text-over-tips';
|
||||
|
||||
// 定义对话项类型
|
||||
interface ConversationItem {
|
||||
key: string;
|
||||
label: string | VNode;
|
||||
icon?: string | VNode;
|
||||
disabled?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const DEFAULT_MENU_CONFIG = [
|
||||
{
|
||||
label: '置顶',
|
||||
key: 'pin',
|
||||
icon: () => <SvgIcon name="svg-pushpin" size={14} class="color-#737478 hover:color-#6D4CFE" />,
|
||||
},
|
||||
{
|
||||
label: '重命名',
|
||||
key: 'rename',
|
||||
icon: <icon-edit size={14} class="color-#737478" />,
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
key: 'delete',
|
||||
icon: <icon-delete size={14} class="color-#F64B31" />,
|
||||
status: 'danger',
|
||||
},
|
||||
] as ConversationItem[];
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Conversations',
|
||||
props: {
|
||||
dataSource: {
|
||||
type: Array as () => ConversationItem[],
|
||||
default: () => [],
|
||||
},
|
||||
activeKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
defaultActiveKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
menu: {
|
||||
type: Array as () => ConversationItem[],
|
||||
default: () => DEFAULT_MENU_CONFIG,
|
||||
},
|
||||
},
|
||||
emits: ['activeChange', 'menuClick', 'update:dataSource', 'update:modelValue', 'rename'],
|
||||
setup(props, { emit, expose }) {
|
||||
const activeKey = ref(props.activeKey || props.defaultActiveKey || '');
|
||||
const localDataSource = ref<ConversationItem[]>([]);
|
||||
const inputRef = ref(null);
|
||||
const menuConfigs = ref<ConversationItem[]>(props.menu ?? DEFAULT_MENU_CONFIG);
|
||||
|
||||
// 处理选中变更
|
||||
const handleActiveChange = (value: string) => {
|
||||
activeKey.value = value;
|
||||
emit('update:modelValue', value);
|
||||
emit('activeChange', value);
|
||||
};
|
||||
const onMenuItemClick = ({ menuInfo, item }) => {
|
||||
const { key } = menuInfo;
|
||||
emit('menuClick', menuInfo);
|
||||
|
||||
switch (key) {
|
||||
case 'rename':
|
||||
item.editing = true;
|
||||
nextTick(() => {
|
||||
inputRef.value.focus();
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const changeItems = () => {
|
||||
emit('update:dataSource', localDataSource.value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.dataSource,
|
||||
(newItems) => {
|
||||
if (newItems) {
|
||||
localDataSource.value = cloneDeep(newItems);
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
const renderItems = () => {
|
||||
return localDataSource.value.map((item, index) => (
|
||||
<div
|
||||
class={`group flex justify-between cursor-pointer items-center p-8px h-40px rounded-8px hover:bg-#F2F3F5 ${
|
||||
activeKey.value === item.key ? 'bg-#F2F3F5' : ''
|
||||
}`}
|
||||
onClick={() => handleActiveChange(item.key)}
|
||||
>
|
||||
{item.editing ? (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
v-model:value={item.label}
|
||||
onBlur={() => {
|
||||
item.editing = false;
|
||||
changeItems();
|
||||
emit('rename', item);
|
||||
}}
|
||||
onPressEnter={() => {
|
||||
item.editing = false;
|
||||
}}
|
||||
class="flex-1"
|
||||
/>
|
||||
) : (
|
||||
<TextoverTips context={item.label} class="flex-1" placement="bottom" />
|
||||
)}
|
||||
<Dropdown
|
||||
class="p-0"
|
||||
overlayClassName="xt-conversations-dropdown"
|
||||
placement="bottomRight"
|
||||
v-slots={{
|
||||
overlay: () => (
|
||||
<Menu onClick={(menuInfo: MenuProps) => onMenuItemClick({ menuInfo, item })}>
|
||||
{menuConfigs.value.map((menuItem) => (
|
||||
<Menu.Item key={menuItem.key} icon={menuItem.icon} class={`${menuItem.status || ''}`}>
|
||||
{menuItem.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<icon-more size={16} class="color-#737478 cursor-pointer ml-8px opacity-0 group-hover:opacity-100" />
|
||||
</Dropdown>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
|
||||
return () => <div className="xt-conversations-container">{renderItems()}</div>;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './style.scss';
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.xt-conversations-dropdown {
|
||||
.ant-dropdown-menu {
|
||||
padding: 4px 0;
|
||||
.ant-dropdown-menu-item {
|
||||
padding: 0 12px;
|
||||
min-width: 124px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:hover {
|
||||
background: var(--BG-200, #f2f3f5);
|
||||
}
|
||||
&.danger {
|
||||
color: var(--RED-600, #f64b31);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
13
src/components/xt-chat/conversations/style.scss
Normal file
13
src/components/xt-chat/conversations/style.scss
Normal file
@ -0,0 +1,13 @@
|
||||
.xt-conversations-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
:deep(.overflow-text) {
|
||||
color: var(--Text-1, #211f24);
|
||||
font-family: $font-family-regular;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user