chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,443 @@
<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 是 stringselectedOptions 是选中的选项数组
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>