444 lines
13 KiB
Plaintext
444 lines
13 KiB
Plaintext
<template>
|
||
<view class="variables-box flex flex-col" v-if="variables?.length">
|
||
<!-- header -->
|
||
<view class="header">
|
||
<text class="title">{{ t("Mobile.Conversation.settingsTitle") }}</text>
|
||
<text class="iconfont icon-a-Chevrondown down-icon" :class="{'rotate-90': !isOpen}" @click="handleOpenClose" />
|
||
</view>
|
||
<!-- 内容区域 -->
|
||
<view :class="{'close-form': !isOpen, 'conversation-container': isOpen}">
|
||
<view class="form-box">
|
||
<view class="uni-form-item" v-for="item in variables" :key="item.key">
|
||
<!-- 名称 -->
|
||
<view class="form-item-title">
|
||
<text class="display-name">{{ item.displayName }}</text>
|
||
<text v-if="item.require" class="required">{{
|
||
t("Mobile.Common.requiredMark")
|
||
}}</text>
|
||
</view>
|
||
<!-- textarea -->
|
||
<textarea
|
||
v-if="item.inputType === InputTypeEnum.Paragraph"
|
||
:placeholder="item.description || t('Mobile.Common.pleaseInput')"
|
||
class="uni-input"
|
||
:class="{'disabled-style': disabled}"
|
||
:disabled="disabled"
|
||
:value="formSourceData[item.name]"
|
||
placeholder-class="uni-placeholder"
|
||
:maxlength="-1"
|
||
@input="(e) => handleChangeInput(e, item)" />
|
||
<!-- 级联下拉选择器(单选、多选) -->
|
||
<view
|
||
v-else-if="item.inputType === InputTypeEnum.Select || item.inputType === InputTypeEnum.MultipleSelect"
|
||
class="uni-input flex flex-row items-center content-between"
|
||
@click="handleSelect(item)"
|
||
>
|
||
<scroll-view
|
||
v-if="formSourceData[item.name]?.length"
|
||
class="flex-1 flex flex-row items-center"
|
||
direction="horizontal"
|
||
:scroll-with-animation="true"
|
||
:show-scrollbar="false"
|
||
scroll-x>
|
||
<!-- 循环渲染 -->
|
||
<template v-for="(info, index) in formSourceData[item.name]" :key="info">
|
||
<!-- 多选样式 -->
|
||
<view class="multiple-box" v-if="item.inputType === InputTypeEnum.MultipleSelect">
|
||
<text :class="{'disabled-style': disabled}">{{`${info?.label || info}`}}</text>
|
||
</view>
|
||
<!-- 单选样式 -->
|
||
<template v-else>
|
||
<text :class="{'disabled-style': disabled}">{{`${info?.label || info}`}}</text>
|
||
<text
|
||
:class="{'disabled-style': disabled}"
|
||
v-if="index !== formSourceData[item.name].length - 1"
|
||
>
|
||
{{ t("Mobile.Common.optionSeparator") }}
|
||
</text>
|
||
</template>
|
||
</template>
|
||
</scroll-view>
|
||
<text v-else class="uni-placeholder">{{
|
||
t("Mobile.Common.pleaseSelect")
|
||
}}</text>
|
||
<text class="iconfont icon-a-Chevrondown down-icon ml-10" />
|
||
</view>
|
||
<!-- 输入框 -->
|
||
<input
|
||
v-else
|
||
:disabled="disabled"
|
||
:value="formSourceData[item.name]"
|
||
:placeholder="item.description || t('Mobile.Common.pleaseInput')"
|
||
:type="item.inputType === InputTypeEnum.Number ? 'number' : 'text'"
|
||
class="uni-input"
|
||
:class="{'disabled-style': disabled}"
|
||
placeholder-class="uni-placeholder"
|
||
@input="(e) => handleChangeInput(e, item)"
|
||
/>
|
||
</view>
|
||
</view>
|
||
<view v-if="disabled" class="desc-box">
|
||
<text class="desc">{{
|
||
t("Mobile.Conversation.settingsLocked")
|
||
}}</text>
|
||
</view>
|
||
<!-- 单选级联选择器 -->
|
||
<l-cascader
|
||
v-if="visible"
|
||
:visible="visible"
|
||
v-model="cascaderValue"
|
||
:title="t('Mobile.Common.pleaseSelect')"
|
||
:options="options"
|
||
@close="handleCancelSelect"
|
||
@change="onChange" />
|
||
|
||
<!-- 多选级联选择器 -->
|
||
<l-cascader
|
||
v-if="visibleMultiple"
|
||
:visible="visibleMultiple"
|
||
v-model:multipleValue="cascaderMultipleValue"
|
||
:multiple="isMultiple"
|
||
:title="t('Mobile.Common.pleaseSelect')"
|
||
:options="options"
|
||
:confirmBtn="t('Mobile.Common.ok')"
|
||
@close="handleCancelSelect"
|
||
@finish="handleFinish" />
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { BindConfigWithSub } from '@/types/interfaces/common';
|
||
import { InputTypeEnum } from '@/types/enums/agent';
|
||
import lCascader from '@/uni_modules/lime-cascader/components/l-cascader/l-cascader.uvue';
|
||
import { useI18n } from "@/utils/i18n";
|
||
|
||
const { t } = useI18n();
|
||
|
||
const props = defineProps({
|
||
variables: {
|
||
type: Array as PropType<BindConfigWithSub[]>,
|
||
default: () => [],
|
||
},
|
||
userFillVariables: {
|
||
type: Object,
|
||
default: () => {},
|
||
},
|
||
isFilled: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
disabled: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
});
|
||
|
||
// 是否打开表单
|
||
const isOpen = ref<boolean>(true);
|
||
// 是否显示单选级联选择器
|
||
const visible = ref<boolean>(false);
|
||
// 是否显示多选级联选择器
|
||
const visibleMultiple = ref<boolean>(false);
|
||
// 是否多选
|
||
const isMultiple = ref<boolean>(false);
|
||
// 级联选择器选项
|
||
const options = ref<any[]>([]);
|
||
// 已选择项的值(单选模式)
|
||
const cascaderValue = ref<string>('');
|
||
// 已选择项的值(多选模式)
|
||
const cascaderMultipleValue = ref<string[]>([]);
|
||
// 当前选择项的名称
|
||
const currentName = ref<string>('');
|
||
// 表单数据
|
||
const formData = reactive({});
|
||
// 已选择项的源数据
|
||
const formSourceData = reactive({});
|
||
|
||
const emit = defineEmits(['toggleWholeDisabled'])
|
||
|
||
defineExpose({
|
||
variableParams: formData
|
||
})
|
||
|
||
/**
|
||
* 监听智能体用户已填写的变量参数
|
||
*/
|
||
watch(() => props.userFillVariables, (newVal) => {
|
||
// 判断newVal是否为空对象
|
||
const isEmptyObject = newVal && typeof newVal === 'object' && Object.keys(newVal).length === 0
|
||
|
||
if (isEmptyObject) {
|
||
// 如果是空对象,清空formSourceData的所有属性,保持响应性
|
||
for (const key in formSourceData) {
|
||
delete formSourceData[key]
|
||
}
|
||
|
||
// 如果是空对象,清空formData的所有属性,保持响应性
|
||
for (const key in formData) {
|
||
delete formData[key]
|
||
}
|
||
} else if (newVal && typeof newVal === 'object') {
|
||
// 如果不是空对象,更新formSourceData
|
||
Object.assign(formSourceData, newVal)
|
||
Object.assign(formData, newVal)
|
||
} else {
|
||
// 如果newVal为null或undefined,也清空formSourceData
|
||
for (const key in formSourceData) {
|
||
delete formSourceData[key]
|
||
}
|
||
|
||
// 如果是空对象,清空formData的所有属性,保持响应性
|
||
for (const key in formData) {
|
||
delete formData[key]
|
||
}
|
||
}
|
||
}, { deep: true, immediate: true })
|
||
|
||
// 必填变量参数name列表
|
||
const requiredNameList = computed(() => {
|
||
return props.variables
|
||
?.filter((item: BindConfigWithSub) => !item.systemVariable && item.require)
|
||
?.map((item: BindConfigWithSub) => item.name) || []
|
||
})
|
||
|
||
// 监听必填参数列表和变量参数变化
|
||
watch([requiredNameList, formData], () => {
|
||
// 没有必填参数时,不禁用发送按钮
|
||
if (requiredNameList.value?.length > 0) {
|
||
let isSameName = true
|
||
for (const item of requiredNameList.value) {
|
||
if (!formData[item] || formData[item]?.length === 0) {
|
||
isSameName = false
|
||
break
|
||
}
|
||
}
|
||
|
||
// 未填写必填参数,禁用发送按钮
|
||
emit('toggleWholeDisabled', !isSameName)
|
||
}
|
||
}, { deep: true, immediate: true })
|
||
|
||
/**
|
||
* 级联选择器选择后
|
||
* @param selectedPaths 选中的路径数组 输出: [['440000', '440300', '440305'], ['440000', '440300', '440306']]
|
||
* @param selectedOptions 选中的源数据 // 输出:
|
||
* [
|
||
* { value: '440305', label: '南山区', ... },
|
||
* { value: '440306', label: '龙岗区', ... }
|
||
* ]
|
||
*/
|
||
const handleFinish = (selectedPaths: string[][], selectedOptions: UTSJSONObject[]) => {
|
||
// 输出: [['440000', '440300', '440305'], ['440000', '440300', '440306']]
|
||
formData[currentName.value] = selectedPaths || []
|
||
formSourceData[currentName.value] = selectedOptions || []
|
||
visibleMultiple.value = false
|
||
isMultiple.value = false
|
||
}
|
||
|
||
// 打开关闭表单
|
||
const handleOpenClose = () => {
|
||
isOpen.value = !isOpen.value
|
||
}
|
||
|
||
// 级联选择器选择后(单选模式)
|
||
const onChange = (_: string | string[], selectedOptions: any[]) => {
|
||
if (!isMultiple.value) {
|
||
visible.value = false
|
||
// 单选模式:value 是 string,selectedOptions 是选中的选项数组
|
||
formData[currentName.value] = selectedOptions?.map(item => item.value) || []
|
||
formSourceData[currentName.value] = selectedOptions || []
|
||
}
|
||
}
|
||
|
||
// 点击后打开级联选择器
|
||
const handleSelect = (item) => {
|
||
// 如果禁用,则不打开级联选择器
|
||
if (props.disabled) {
|
||
return
|
||
}
|
||
// 当前选择项的名称
|
||
currentName.value = item.name
|
||
if (item.inputType === InputTypeEnum.MultipleSelect) {
|
||
visibleMultiple.value = true
|
||
} else {
|
||
// 显示级联选择器
|
||
visible.value = true
|
||
}
|
||
// 是否多选
|
||
isMultiple.value = item.inputType === InputTypeEnum.MultipleSelect
|
||
// 下拉选择项配置
|
||
options.value = item.selectConfig?.options || []
|
||
// 已选择项的源数据中获取当前项的值
|
||
const currentList = formSourceData[item.name] || []
|
||
|
||
if (isMultiple.value) {
|
||
// 多选模式:初始化多选值
|
||
if (currentList.length === 0) {
|
||
cascaderMultipleValue.value = []
|
||
} else {
|
||
cascaderMultipleValue.value = currentList.map((item: any) => item.value || '')
|
||
}
|
||
} else {
|
||
// 单选模式:初始化单选值
|
||
if (currentList.length === 0) {
|
||
cascaderValue.value = ''
|
||
} else {
|
||
const lastItem = currentList[currentList.length - 1]
|
||
cascaderValue.value = lastItem.value || ''
|
||
}
|
||
}
|
||
}
|
||
|
||
// 取消级联选择
|
||
const handleCancelSelect = () => {
|
||
// 当前选择项的名称
|
||
currentName.value = ''
|
||
// 隐藏级联选择器
|
||
visible.value = false
|
||
visibleMultiple.value = false
|
||
isMultiple.value = false
|
||
// 下拉选择项配置
|
||
options.value = [];
|
||
// 已选择项的值
|
||
cascaderValue.value = '';
|
||
cascaderMultipleValue.value = [];
|
||
}
|
||
|
||
// 输入框输入值
|
||
const handleChangeInput = (event: UniInputChangeEvent, item) => {
|
||
formData[item.name] = event.detail.value
|
||
formSourceData[item.name] = event.detail.value
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.variables-box {
|
||
max-height: 50%;
|
||
border-radius: 10rpx;
|
||
background-color: #fff;
|
||
border: 1rpx solid #e5e5e5;
|
||
box-sizing: border-box;
|
||
margin: 0 32rpx;
|
||
|
||
.header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
font-weight: bold;
|
||
padding: 18rpx 20rpx;
|
||
|
||
.title {
|
||
font-size: 28rpx;
|
||
color: #15171F;
|
||
}
|
||
}
|
||
|
||
.down-icon {
|
||
color: #95979c;
|
||
user-select: none;
|
||
font-size: 32rpx;
|
||
transition: transform 0.3s ease-in-out;
|
||
|
||
&.rotate-90 {
|
||
transform: rotate(-90deg);
|
||
}
|
||
}
|
||
|
||
.conversation-container {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-top: 20rpx;
|
||
border-top: 1rpx solid #e5e5e5;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.close-form {
|
||
transition: height 0.3s ease-in-out;
|
||
height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.form-box {
|
||
flex: 1;
|
||
max-height: 400rpx;
|
||
overflow-y: auto;
|
||
padding: 0 20rpx;
|
||
|
||
.uni-form-item {
|
||
padding: 16rpx 0;
|
||
|
||
.form-item-title {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
|
||
.display-name {
|
||
font-size: 28rpx;
|
||
color: #15171F;
|
||
}
|
||
|
||
.required {
|
||
margin-left: 4rpx;
|
||
color: #FF0000;
|
||
}
|
||
}
|
||
|
||
.uni-input {
|
||
width: 100%;
|
||
height: 72rpx;
|
||
line-height: 40rpx;
|
||
background-color: rgba(12,20,40,0.04);
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
color: #15171F;
|
||
padding: 16rpx;
|
||
margin-top: 8rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
// 禁用样式
|
||
.disabled-style {
|
||
background-color: #f5f5f5;
|
||
color: #999;
|
||
}
|
||
|
||
.uni-placeholder {
|
||
color: #6b6b75;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.multiple-box {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: rgba(0, 0, 0, 0.06);
|
||
padding: 4rpx 12rpx;
|
||
margin-right: 16rpx;
|
||
border-radius: 8rpx;
|
||
|
||
.disabled-style {
|
||
background-color: transparent;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.desc-box {
|
||
display: flex;
|
||
justify-content: center;
|
||
height: 70rpx;
|
||
padding: 0 20rpx;
|
||
box-sizing: border-box;
|
||
|
||
.desc {
|
||
color: #e5e5e5;
|
||
font-size: 28rpx;
|
||
}
|
||
}
|
||
}
|
||
</style>
|