feat: 钉钉、企业微信webbook、企业微信应用api,问题:交互
This commit is contained in:
170
apps/web-antd/src/components/form/components/user-tag-select.vue
Normal file
170
apps/web-antd/src/components/form/components/user-tag-select.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 通用 Tag 选择器(带头像)
|
||||
*
|
||||
* - 基于 antd Select,multiple 模式默认开启,单选传 multiple=false
|
||||
* - 已选项以 Tag 形式展示头像+名字:
|
||||
* - 有 avatar 用图片头像
|
||||
* - 无 avatar 用文本头像(取 label 首字符)
|
||||
* - 下拉选项同样展示头像+名字
|
||||
* - 选项数据:options: { value, label, avatar? }[]
|
||||
*/
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Avatar, Select, Tag } from 'ant-design-vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'UserTagSelect',
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
/** 单个选项结构 */
|
||||
export interface UserTagOption {
|
||||
value: string | number;
|
||||
label: string;
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 已选值数组(多选)或单值(单选时仍用数组包装) */
|
||||
modelValue?: (string | number)[];
|
||||
/** 选项列表 */
|
||||
options: UserTagOption[];
|
||||
/** 是否多选,默认 true */
|
||||
multiple?: boolean;
|
||||
/** 最大选择数,超出截断 */
|
||||
maxCount?: number;
|
||||
placeholder?: string;
|
||||
allowClear?: boolean;
|
||||
showSearch?: boolean;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{
|
||||
modelValue: () => [],
|
||||
multiple: true,
|
||||
maxCount: 99,
|
||||
placeholder: '请选择',
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
disabled: false,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [v: (string | number)[]];
|
||||
}>();
|
||||
|
||||
// 双向绑定:useVModel 自动处理 modelValue 的读写
|
||||
const innerValue = useVModel(props, 'modelValue', emit, {
|
||||
defaultValue: [] as (string | number)[],
|
||||
passive: true,
|
||||
});
|
||||
|
||||
/** 用于 Select 的 mode,单选时不传 mode */
|
||||
const selectMode = computed(() => (props.multiple ? 'multiple' : undefined));
|
||||
|
||||
/** 按 value 索引选项,方便 tagRender/option 插槽快速查头像与名字 */
|
||||
const optionMap = computed(() => {
|
||||
const map = new Map<string | number, UserTagOption>();
|
||||
for (const opt of props.options) {
|
||||
map.set(opt.value, opt);
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
/** 取某 value 对应的展示名 */
|
||||
function labelOf(value: string | number) {
|
||||
return optionMap.value.get(value)?.label || String(value);
|
||||
}
|
||||
|
||||
/** 取某 value 对应的头像 */
|
||||
function avatarOf(value: string | number) {
|
||||
return optionMap.value.get(value)?.avatar;
|
||||
}
|
||||
|
||||
/** 取首字符(文本头像) */
|
||||
function firstChar(label?: string) {
|
||||
return (label || '?').trim().charAt(0) || '?';
|
||||
}
|
||||
|
||||
/** 选项过滤:按 label 包含输入关键字 */
|
||||
function filterOption(input: string, option: any) {
|
||||
const v = String(option?.label || '').toLowerCase();
|
||||
return v.includes((input || '').toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* change 处理:
|
||||
* - 单选:把单值包成数组
|
||||
* - 多选:超 maxCount 截断
|
||||
*/
|
||||
function onChange(v: any) {
|
||||
if (!props.multiple) {
|
||||
innerValue.value = v == null ? [] : [v];
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(v) && v.length > props.maxCount) {
|
||||
v = v.slice(0, props.maxCount);
|
||||
}
|
||||
innerValue.value = v;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select
|
||||
:value="innerValue as any"
|
||||
:mode="selectMode"
|
||||
:options="options"
|
||||
:placeholder="placeholder"
|
||||
:allow-clear="allowClear"
|
||||
:show-search="showSearch"
|
||||
:filter-option="filterOption"
|
||||
:disabled="disabled"
|
||||
:max-tag-count="10"
|
||||
option-filter-prop="label"
|
||||
style="width: 100%"
|
||||
@change="onChange"
|
||||
>
|
||||
<!-- 多选已选项展示头像+名字 -->
|
||||
<template v-if="multiple" #tagRender="{ value, closable, onClose }">
|
||||
<Tag :closable="closable" @close="onClose" class="user-tag-chip">
|
||||
<Avatar :size="16" :src="avatarOf(value)">
|
||||
{{ firstChar(labelOf(value)) }}
|
||||
</Avatar>
|
||||
<span class="user-tag-name">{{ labelOf(value) }}</span>
|
||||
</Tag>
|
||||
</template>
|
||||
|
||||
<!-- 下拉选项展示头像+名字 -->
|
||||
<template #option="{ label, avatar }">
|
||||
<div class="user-tag-option">
|
||||
<Avatar :size="20" :src="avatar">{{ firstChar(label) }}</Avatar>
|
||||
<span>{{ label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.user-tag-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.user-tag-name {
|
||||
max-width: 80px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.user-tag-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -40,8 +40,8 @@ defineProps<{
|
||||
flex-direction: column;
|
||||
width: 200px;
|
||||
height: 100%;
|
||||
border-right: 1px solid #e5e6eb;
|
||||
background: #fafbfc;
|
||||
border-right: 1px solid var(--ant-color-border-secondary, #e5e6eb);
|
||||
background: var(--ant-color-fill-quaternary, #fafbfc);
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
@@ -49,16 +49,16 @@ defineProps<{
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
border-bottom: 1px solid var(--ant-color-border-secondary, #e5e6eb);
|
||||
font-size: 13px;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
color: var(--ant-color-text, #1d2129);
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: #86909c;
|
||||
color: var(--ant-color-text-tertiary, #86909c);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
@@ -75,19 +75,19 @@ defineProps<{
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 6px;
|
||||
border: 1px dashed #c9cdd4;
|
||||
border: 1px dashed var(--ant-color-border, #c9cdd4);
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
background: var(--ant-color-bg-container, #fff);
|
||||
font-size: 13px;
|
||||
color: #4e5969;
|
||||
color: var(--ant-color-text-secondary, #4e5969);
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: #165dff;
|
||||
color: #165dff;
|
||||
background: #f2f7ff;
|
||||
border-color: var(--ant-color-primary, #165dff);
|
||||
color: var(--ant-color-primary, #165dff);
|
||||
background: var(--ant-color-primary-bg, #f2f7ff);
|
||||
}
|
||||
|
||||
&:active {
|
||||
@@ -102,34 +102,7 @@ defineProps<{
|
||||
.empty-hint {
|
||||
padding: 24px 12px;
|
||||
text-align: center;
|
||||
color: #c9cdd4;
|
||||
color: var(--ant-color-text-quaternary, #c9cdd4);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dark {
|
||||
.block-library {
|
||||
background: #1f1f1f;
|
||||
border-right-color: #374151;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
border-bottom-color: #374151;
|
||||
|
||||
.title {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
}
|
||||
|
||||
.block-item {
|
||||
background: #374151;
|
||||
border-color: #4b5563;
|
||||
color: #d1d5db;
|
||||
|
||||
&:hover {
|
||||
border-color: #60a5fa;
|
||||
color: #60a5fa;
|
||||
background: rgba(96, 165, 250, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -159,7 +159,7 @@ function handleRemove(instanceId: string) {
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: #fff;
|
||||
background: var(--ant-color-bg-container, #fff);
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
@@ -167,16 +167,16 @@ function handleRemove(instanceId: string) {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
border-bottom: 1px solid var(--ant-color-border-secondary, #e5e6eb);
|
||||
font-size: 13px;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
color: var(--ant-color-text, #1d2129);
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: #86909c;
|
||||
color: var(--ant-color-text-tertiary, #86909c);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
@@ -199,22 +199,22 @@ function handleRemove(instanceId: string) {
|
||||
font-size: 12px;
|
||||
|
||||
.group-label {
|
||||
color: #4e5969;
|
||||
color: var(--ant-color-text-secondary, #4e5969);
|
||||
font-weight: 500;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.group-count {
|
||||
color: #86909c;
|
||||
color: var(--ant-color-text-tertiary, #86909c);
|
||||
}
|
||||
}
|
||||
|
||||
.group-dropzone {
|
||||
min-height: 60px;
|
||||
padding: 8px;
|
||||
border: 2px dashed #d9d9d9;
|
||||
border: 2px dashed var(--ant-color-border, #d9d9d9);
|
||||
border-radius: 6px;
|
||||
background: #fafbfc;
|
||||
background: var(--ant-color-fill-quaternary, #fafbfc);
|
||||
}
|
||||
|
||||
.empty-dropzone {
|
||||
@@ -222,7 +222,7 @@ function handleRemove(instanceId: string) {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 44px;
|
||||
color: #c9cdd4;
|
||||
color: var(--ant-color-text-quaternary, #c9cdd4);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -232,9 +232,9 @@ function handleRemove(instanceId: string) {
|
||||
justify-content: space-between;
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 6px;
|
||||
border: 1px solid #e5e6eb;
|
||||
border: 1px solid var(--ant-color-border-secondary, #e5e6eb);
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
background: var(--ant-color-bg-container, #fff);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
|
||||
@@ -243,12 +243,12 @@ function handleRemove(instanceId: string) {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #165dff;
|
||||
border-color: var(--ant-color-primary, #165dff);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: #165dff;
|
||||
background: #f2f7ff;
|
||||
border-color: var(--ant-color-primary, #165dff);
|
||||
background: var(--ant-color-primary-bg, #f2f7ff);
|
||||
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.15);
|
||||
}
|
||||
}
|
||||
@@ -268,13 +268,13 @@ function handleRemove(instanceId: string) {
|
||||
|
||||
.instance-label {
|
||||
font-size: 13px;
|
||||
color: #1d2129;
|
||||
color: var(--ant-color-text, #1d2129);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.instance-summary {
|
||||
font-size: 11px;
|
||||
color: #86909c;
|
||||
color: var(--ant-color-text-tertiary, #86909c);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -286,52 +286,15 @@ function handleRemove(instanceId: string) {
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: #86909c;
|
||||
color: var(--ant-color-text-tertiary, #86909c);
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: #f53f3f;
|
||||
background: var(--ant-color-error, #f53f3f);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.canvas {
|
||||
background: #1f1f1f;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
border-bottom-color: #374151;
|
||||
|
||||
.title {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
}
|
||||
|
||||
.group-dropzone {
|
||||
background: #1f1f1f;
|
||||
border-color: #374151;
|
||||
}
|
||||
|
||||
.instance-item {
|
||||
background: #374151;
|
||||
border-color: #4b5563;
|
||||
|
||||
&:hover {
|
||||
border-color: #60a5fa;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: #60a5fa;
|
||||
background: rgba(96, 165, 250, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.instance-label {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -68,7 +68,8 @@ async function handleCopy() {
|
||||
flex-direction: column;
|
||||
width: 280px;
|
||||
height: 100%;
|
||||
border-left: 1px solid #e5e6eb;
|
||||
border-left: 1px solid var(--ant-color-border-secondary, #e5e6eb);
|
||||
/* JSON 代码面板保留 VS Code Dark+ 风格暗底(无论浅色/暗色主题) */
|
||||
background: #1e1e1e;
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ watch(
|
||||
read-only
|
||||
/>
|
||||
<GalleryPickLink
|
||||
:accept-types="field.acceptTypes || [1]"
|
||||
:accept-types="field.acceptTypes || [0]"
|
||||
@select="(urls) => setFixedValue(field.name, urls[0] || '')"
|
||||
/>
|
||||
</div>
|
||||
@@ -254,7 +254,7 @@ watch(
|
||||
read-only
|
||||
/>
|
||||
<GalleryPickLink
|
||||
:accept-types="field.acceptTypes || [1]"
|
||||
:accept-types="field.acceptTypes || [0]"
|
||||
@select="(urls) => setInstanceValue(field.name, urls[0] || '')"
|
||||
/>
|
||||
</div>
|
||||
@@ -278,8 +278,8 @@ watch(
|
||||
flex-direction: column;
|
||||
width: 280px;
|
||||
height: 100%;
|
||||
border-left: 1px solid #e5e6eb;
|
||||
background: #fff;
|
||||
border-left: 1px solid var(--ant-color-border-secondary, #e5e6eb);
|
||||
background: var(--ant-color-bg-container, #fff);
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
@@ -287,16 +287,16 @@ watch(
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
border-bottom: 1px solid var(--ant-color-border-secondary, #e5e6eb);
|
||||
font-size: 13px;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
color: var(--ant-color-text, #1d2129);
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: #86909c;
|
||||
color: var(--ant-color-text-tertiary, #86909c);
|
||||
font-size: 11px;
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
@@ -318,10 +318,10 @@ watch(
|
||||
.section-title {
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid #f2f3f5;
|
||||
border-bottom: 1px solid var(--ant-color-split, #f2f3f5);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #4e5969;
|
||||
color: var(--ant-color-text-secondary, #4e5969);
|
||||
}
|
||||
|
||||
.form-row {
|
||||
@@ -332,11 +332,11 @@ watch(
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
color: #4e5969;
|
||||
color: var(--ant-color-text-secondary, #4e5969);
|
||||
|
||||
.required {
|
||||
margin-left: 2px;
|
||||
color: #f53f3f;
|
||||
color: var(--ant-color-error, #f53f3f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,28 +345,4 @@ watch(
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.dark {
|
||||
.property-panel {
|
||||
background: #1f1f1f;
|
||||
border-left-color: #374151;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
border-bottom-color: #374151;
|
||||
|
||||
.title {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
border-bottom-color: #374151;
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
color: #d1d5db;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -493,10 +493,10 @@ defineExpose({
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
border: 1px solid #e5e6eb;
|
||||
border: 1px solid var(--ant-color-border-secondary, #e5e6eb);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
background: var(--ant-color-bg-container, #fff);
|
||||
}
|
||||
|
||||
.editor-toolbar {
|
||||
@@ -504,13 +504,13 @@ defineExpose({
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
background: #fafbfc;
|
||||
border-bottom: 1px solid var(--ant-color-border-secondary, #e5e6eb);
|
||||
background: var(--ant-color-fill-quaternary, #fafbfc);
|
||||
font-size: 13px;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
color: var(--ant-color-text, #1d2129);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,20 +519,4 @@ defineExpose({
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.dark {
|
||||
.template-editor {
|
||||
background: #1f1f1f;
|
||||
border-color: #374151;
|
||||
}
|
||||
|
||||
.editor-toolbar {
|
||||
background: #1f1f1f;
|
||||
border-bottom-color: #374151;
|
||||
|
||||
.title {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user