175 lines
4.7 KiB
Vue
175 lines
4.7 KiB
Vue
<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/index.vue';
|
|
import TextoverTips from '@/components/text-over-tips/index.vue';
|
|
|
|
// 定义对话项类型
|
|
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 = (item: ConversationItem) => {
|
|
const { value } = item;
|
|
activeKey.value = value;
|
|
emit('update:modelValue', value);
|
|
emit('activeChange', item);
|
|
};
|
|
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)}
|
|
>
|
|
{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>
|