Compare commits

..

12 Commits

Author SHA1 Message Date
X1627315083@163.com
c8f7d157f4 3d模型优化销毁功能 2026-05-20 15:12:50 +08:00
lzp
8acb5b4ce5 12 2026-05-05 09:49:21 +08:00
lzp
1dd36b1b8c 1 2026-05-05 09:44:20 +08:00
2bca08ed97 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/FiDA_Front 2026-05-04 17:09:00 +08:00
ba63d16d60 feat: 隐藏生成参数调节按钮 2026-05-04 17:08:58 +08:00
X1627315083@163.com
f24a9afe5c 3d 默认模型改为Normal 2026-05-04 16:57:37 +08:00
4d1d082fae style: 标签排版 2026-05-04 14:58:02 +08:00
c2f0f82218 style: 对话列表标签排版 2026-05-04 14:50:50 +08:00
620962b9ee feat: 参数标签逻辑修改&对话列表显示标签 2026-05-04 13:47:51 +08:00
89aab7e960 style: 禁用状态文字颜色 2026-05-04 11:09:20 +08:00
1078961608 feat: 选择trending report之后清除其他参数 2026-05-04 09:41:58 +08:00
d073008736 bugfix: 清除标签后同步style弹窗 2026-04-30 15:49:23 +08:00
14 changed files with 410 additions and 43 deletions

View File

@@ -20,7 +20,7 @@
const stateManager = inject('stateManager') as any const stateManager = inject('stateManager') as any
const data = reactive({ const data = reactive({
url: stateManager.getSuperiorNodeImage(attrs.node?.data?.superiorID), url: stateManager.getSuperiorNodeImage(attrs.node?.data?.superiorID),
mode: 'Advanced', mode: 'Normal',
}) })
const modeList = ref([ const modeList = ref([
{ value: 'Advanced', label: t('FlowCanvas.advancedMode') }, { value: 'Advanced', label: t('FlowCanvas.advancedMode') },

View File

@@ -41,6 +41,9 @@ const captureView = ()=>{
onMounted(()=>{ onMounted(()=>{
}) })
onUnmounted(()=>{ onUnmounted(()=>{
console.log('onUnmounted')
threeModel.disposeModel()
threeModel = null
}) })
defineExpose({open,captureView}) defineExpose({open,captureView})
</script> </script>

View File

@@ -24,7 +24,7 @@ export class ThreeManager {
camera: THREE.PerspectiveCamera;//相机对象 camera: THREE.PerspectiveCamera;//相机对象
renderer: THREE.WebGLRenderer;//渲染器对象 renderer: THREE.WebGLRenderer;//渲染器对象
controls: OrbitControls;//轨道控制器对象 controls: OrbitControls;//轨道控制器对象
animationId: number | null = null;
pointLight: THREE.AmbientLight;//环境光对象 pointLight: THREE.AmbientLight;//环境光对象
studioLights: any;//工作室光对象数组 studioLights: any;//工作室光对象数组
@@ -316,15 +316,14 @@ export class ThreeManager {
} }
operation(){ operation(){
let this_ = this
const animate = () => { const animate = () => {
requestAnimationFrame(animate); this.animationId = requestAnimationFrame(animate);
this.controls.update(); this.controls?.update();
this.updateStudioLighting(); this.updateStudioLighting();
this.updateImagePipeline(); this.updateImagePipeline();
this.renderer.render(this.scene, this.camera); this.renderer?.render(this.scene, this.camera);
} };
animate(); animate();
} }
// 释放模型资源 // 释放模型资源
@@ -343,7 +342,91 @@ export class ThreeManager {
} }
}); });
} }
disposeModel() {
console.log('开始销毁 ThreeManager...');
// 1. 停止动画
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
// 2. 销毁控制器
if (this.controls) {
this.controls.dispose();
this.controls = null;
}
// 3. 销毁模型
if (this.currentModel) {
this.dispose(this.currentModel);
this.scene?.remove(this.currentModel);
this.currentModel = null;
}
// 4. 销毁灯光
if (this.studioLights?.length) {
this.studioLights.forEach(item => {
if (item.light) {
this.scene?.remove(item.light);
item.light.dispose?.();
}
});
this.studioLights = [];
}
// 5. 清理环境光
if (this.pointLight) {
this.scene?.remove(this.pointLight);
this.pointLight = null;
}
// 6. 清理场景
if (this.scene) {
this.scene.traverse((obj) => {
if (obj.isMesh) {
const mesh = obj as THREE.Mesh;
mesh.geometry?.dispose();
if (mesh.material) {
if (Array.isArray(mesh.material)) {
mesh.material.forEach(m => m.dispose());
} else {
mesh.material.dispose();
}
}
}
});
while (this.scene.children.length) {
this.scene.remove(this.scene.children[0]);
}
this.scene = null;
}
// 7. 销毁渲染器
if (this.renderer) {
this.renderer.dispose();
if (this.renderer.domElement?.parentNode) {
this.renderer.domElement.parentNode.removeChild(this.renderer.domElement);
}
this.renderer = null;
}
// 8. 清空 DOM 容器
if (this.threeDom) {
this.threeDom.innerHTML = '';
this.threeDom = null;
}
// 9. 清空其他引用
this.camera = null;
this.v1 = null;
this.camDir = null;
this.camForward = null;
this.camToTarget = null;
this.modelInfo = null;
console.log('ThreeManager 已销毁');
}
exportAsImage(){ exportAsImage(){
return this.renderer.domElement.toDataURL('image/png'); return this.renderer.domElement.toDataURL('image/png');

View File

@@ -41,7 +41,7 @@ export default {
retrievePassword: 'Retrieve password' retrievePassword: 'Retrieve password'
}, },
Nuic: { Nuic: {
hiName: `Hi, {name}, I'm Fiphant.`, hiName: `Hi {name}, I'm Fiphant.`,
nuic1Title: `Lets reveal the creative paths waiting for you.`, nuic1Title: `Lets reveal the creative paths waiting for you.`,
nuic1Tip: `Let's set up your profile. A few quick details will help Fiphant understand<br />your needs and find exactly what you're looking for.`, nuic1Tip: `Let's set up your profile. A few quick details will help Fiphant understand<br />your needs and find exactly what you're looking for.`,
letsGo: 'Lets go, Fiphant!', letsGo: 'Lets go, Fiphant!',

View File

@@ -16,6 +16,7 @@ type InitialProjectData = {
useReport:boolean useReport:boolean
needSuggestion:boolean needSuggestion:boolean
quoteList: Array<string> quoteList: Array<string>
parameterTags?: Array<{ kind: string; label: string }>
tempImages: any[] tempImages: any[]
} }
export const useAgentStore = defineStore('agent', () => { export const useAgentStore = defineStore('agent', () => {

View File

@@ -37,8 +37,12 @@
import type { AgentParamsType } from '@/api/agent' import type { AgentParamsType } from '@/api/agent'
import { useUserInfoStore, useProjectStore, useAgentStore } from '@/stores' import { useUserInfoStore, useProjectStore, useAgentStore } from '@/stores'
import MyEvent from '@/utils/myEvent' import MyEvent from '@/utils/myEvent'
import { areaList } from '@/utils/area'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { createStyleOptions, createTypeOptions } from '../../components/input/options'
import type { OptionItem, ParameterTag } from '../../components/input/types'
const { t } = useI18n() const { t } = useI18n()
const route = useRoute() const route = useRoute()
@@ -83,6 +87,59 @@
}) })
const sketchList = ref([]) const sketchList = ref([])
const typeOptions = createTypeOptions()
const areaOptions = areaList
const styleOptions = createStyleOptions()
const getTranslatedOptionLabel = (options: OptionItem[], value?: string) => {
if (!value) return ''
const option = options.find((item) => item.value === value)
return option ? t(option.label) : value
}
const getOptionLabel = (options: OptionItem[], value?: string) => {
if (!value) return ''
return options.find((item) => item.value === value)?.label || value
}
const getProjectParameterTags = (project: any): ParameterTag[] => {
const type = project?.type || ''
const region = project?.region || project?.area || ''
const style = project?.style || ''
const tags: ParameterTag[] = []
if (type) {
tags.push({
kind: 'type',
label: getTranslatedOptionLabel(typeOptions, type)
})
}
if (region) {
tags.push({
kind: 'area',
label: getTranslatedOptionLabel(areaOptions, region)
})
}
if (style) {
tags.push({
kind: 'style',
label: getOptionLabel(styleOptions, style)
})
}
return tags
}
const applyProjectParameterTags = (messages: any[], project: any) => {
const parameterTags = getProjectParameterTags(project)
if (parameterTags.length === 0) return
const firstUserMessage = messages.find((item) => item.isUser || item.role === 'user')
if (firstUserMessage) {
firstUserMessage.parameterTags = parameterTags
}
}
watch( watch(
sketchList, sketchList,
(newVal) => { (newVal) => {
@@ -143,7 +200,8 @@
images: initialData.images, images: initialData.images,
useReport: initialData.useReport, useReport: initialData.useReport,
tempImages: initialData.tempImages, tempImages: initialData.tempImages,
quoteList: initialData.quoteList quoteList: initialData.quoteList,
parameterTags: initialData.parameterTags || []
}) })
// 更新 configParams // 更新 configParams
@@ -181,6 +239,7 @@
tempImages: any[] tempImages: any[]
useReport: boolean useReport: boolean
quoteList: Array<string> quoteList: Array<string>
parameterTags?: Array<{ kind: string; label: string }>
}, },
skipUserMessage = false skipUserMessage = false
) => { ) => {
@@ -199,7 +258,8 @@
id: messageList.value.length + 1, id: messageList.value.length + 1,
text: message.text, text: message.text,
isUser: true, isUser: true,
imageUrls: message.tempImages.concat(message.quoteList) imageUrls: message.tempImages.concat(message.quoteList),
parameterTags: message.parameterTags || []
}) })
} }
@@ -628,7 +688,7 @@
const setChatInfo = (info) => { const setChatInfo = (info) => {
const initialData = agentStore.getInitialProjectData const initialData = agentStore.getInitialProjectData
if (isGenerating.value || initialData) return if (isGenerating.value || initialData) return
console.log('---',info)
const data = info.conversation const data = info.conversation
let project = info.project let project = info.project
if (info.id) { if (info.id) {
@@ -641,7 +701,7 @@
if (project) { if (project) {
params.configParams.type = project.type || '' params.configParams.type = project.type || ''
params.configParams.region = project.area || '' params.configParams.region = project.region || project.area || ''
params.configParams.style = project.style || '' params.configParams.style = project.style || ''
params.configParams.temperature = project.temperature params.configParams.temperature = project.temperature
params.projectID = project.id params.projectID = project.id
@@ -668,6 +728,7 @@
item.text += `<slot slot-name="sketch"></slot>` item.text += `<slot slot-name="sketch"></slot>`
} }
}) })
applyProjectParameterTags(ancestorsList, project)
// console.log('ancestorslist', ancestorsList) // console.log('ancestorslist', ancestorsList)
messageList.value = [...ancestorsList] messageList.value = [...ancestorsList]
params.versionID = current?.id params.versionID = current?.id

View File

@@ -52,20 +52,34 @@
</div> </div>
</div> </div>
</div> --> </div> -->
<div class="message-txt markdown-body flex flex-col"> <div class="message-txt markdown-body">
<span
v-if="parameterTags.length > 0"
class="message-parameter-tags"
>
<span
v-for="tag in parameterTags"
:key="`${tag.kind}-${tag.label}`"
class="message-parameter-tag"
>
<img :src="getParameterTagIcon(tag.kind)" class="parameter-tag-icon" />
<span>{{ tag.label }}</span>
</span>
</span>
<VueMarkdown <VueMarkdown
class="message-markdown"
:custom-attrs="customAttrs" :custom-attrs="customAttrs"
:markdown="content.text" :markdown="content.text"
:rehype-plugins="[rehypeRaw]" :rehype-plugins="[rehypeRaw]"
> >
<template v-slot:s-card="{ children: children, ...attrs }"> <template v-slot:s-card="{ children: children, ...attrs }">
<Card title="Trend Report" @click.native="handleClickReport" /> <Card title="Trend Report" @click="handleClickReport" />
</template> </template>
<template v-slot:s-url="{ children: children }"> <template v-slot:s-url="{ children: children }">
<Url :list="content.webAddress" @click.native="handleClickUrls" /> <Url :list="content.webAddress" @click="handleClickUrls" />
</template> </template>
<template v-slot:s-sketch="{ children: children }"> <template v-slot:s-sketch="{ children: children }">
<Sketch @click.native="handleClickSketch" /> <Sketch @click="handleClickSketch" />
</template> </template>
</VueMarkdown> </VueMarkdown>
<div <div
@@ -116,6 +130,9 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import userThumb from '@/assets/images/user-thumb.jpg' import userThumb from '@/assets/images/user-thumb.jpg'
import agentThumb from '@/assets/images/agent-thumb.png' import agentThumb from '@/assets/images/agent-thumb.png'
import TypeIcon from '@/assets/icons/TypeIcon.svg'
import RegionIcon from '@/assets/icons/RegionIcon.svg'
import StyleIcon from '@/assets/icons/StyleIcon.svg'
import Card from './ReportCard.vue' import Card from './ReportCard.vue'
import Url from './UrlCard.vue' import Url from './UrlCard.vue'
import Sketch from './SketchCard.vue' import Sketch from './SketchCard.vue'
@@ -126,6 +143,7 @@
import rehypeRaw from 'rehype-raw' import rehypeRaw from 'rehype-raw'
import MyEvent from '@/utils/myEvent' import MyEvent from '@/utils/myEvent'
import { useUserInfoStore, useProjectStore } from '@/stores' import { useUserInfoStore, useProjectStore } from '@/stores'
import { ElMessage } from 'element-plus'
const userStore = useUserInfoStore() const userStore = useUserInfoStore()
const projectStore = useProjectStore() const projectStore = useProjectStore()
@@ -136,8 +154,14 @@
return locale.value === 'CHINESE_SIMPLIFIED' return locale.value === 'CHINESE_SIMPLIFIED'
}) })
type ParameterTagKind = 'type' | 'area' | 'style'
type ParameterTag = {
kind: ParameterTagKind
label: string
}
const props = defineProps<{ const props = defineProps<{
content: Object content: any
isLast: Boolean isLast: Boolean
}>() }>()
@@ -174,6 +198,16 @@
return list return list
}) })
const parameterTags = computed<ParameterTag[]>(() => {
return Array.isArray(props.content?.parameterTags) ? props.content.parameterTags : []
})
const getParameterTagIcon = (kind: ParameterTagKind) => {
if (kind === 'type') return TypeIcon
if (kind === 'area') return RegionIcon
return StyleIcon
}
const customAttrs: CustomAttrs = { const customAttrs: CustomAttrs = {
img: { img: {
style: 'max-width: 100%;' style: 'max-width: 100%;'
@@ -348,6 +382,39 @@
width: fit-content; width: fit-content;
max-width: 82%; max-width: 82%;
} }
.message-parameter-tags {
display: inline;
}
.message-parameter-tag {
display: inline-flex;
align-items: center;
vertical-align: middle;
height: 3rem;
max-width: 24rem;
padding: 0 1rem;
margin: 0 0.8rem 0.4rem 0;
border: 0.1rem solid #0000001a;
border-radius: 2.2rem;
column-gap: 0.8rem;
font-family: 'Medium';
font-weight: 500;
font-size: 1.2rem;
color: #0d0d0d;
background-color: #fff;
box-sizing: border-box;
.parameter-tag-icon {
width: 1.2rem;
height: 1.2rem;
flex-shrink: 0;
object-fit: contain;
}
span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.web-address { .web-address {
width: fit-content; width: fit-content;
min-width: 22.5rem; min-width: 22.5rem;
@@ -443,6 +510,18 @@
<style lang="less"> <style lang="less">
.message-txt { .message-txt {
user-select: text; user-select: text;
.message-markdown,
> div:not(.web-address) {
display: inline;
}
.message-markdown > p:first-child,
> div:not(.web-address) > p:first-child {
display: inline;
margin-top: 0;
}
ul { ul {
list-style-position: inside; list-style-position: inside;
} }

View File

@@ -36,9 +36,10 @@
:setting-title="$t('Input.styleTitle')" :setting-title="$t('Input.styleTitle')"
:confirm-text="$t('Input.confirm')" :confirm-text="$t('Input.confirm')"
:create-text="$t('Input.createProject')" :create-text="$t('Input.createProject')"
:parameters-disabled="isReportSelected"
:translate="translate" :translate="translate"
@file-change="handleFileChange" @file-change="handleFileChange"
@toggle-report="toggleReportTag()" @toggle-report="handleToggleReportTag"
@create="handleCreateProject" @create="handleCreateProject"
@send="handleSendAgent" @send="handleSendAgent"
/> />
@@ -48,7 +49,7 @@
v-if="!isAgentMode" v-if="!isAgentMode"
:is-cn="isCn" :is-cn="isCn"
:label="$t('Input.trendingReport')" :label="$t('Input.trendingReport')"
@click="toggleReportTag()" @click="handleToggleReportTag"
/> />
<Preview v-model="showPreview" :url="previewUrl" /> <Preview v-model="showPreview" :url="previewUrl" />
</div> </div>
@@ -70,11 +71,12 @@
import { import {
createSettingOptions, createSettingOptions,
createStyleOptions, createStyleOptions,
createTypeOptions createTypeOptions,
optionTagOrder
} from './input/options' } from './input/options'
import { useInputEditor } from './input/useInputEditor' import { useInputEditor } from './input/useInputEditor'
import { useInputImages } from './input/useInputImages' import { useInputImages } from './input/useInputImages'
import type { OptionItem, SettingOption } from './input/types' import type { OptionItem, OptionTagKind, ParameterTag, SettingOption } from './input/types'
const router = useRouter() const router = useRouter()
const agentStore = useAgentStore() const agentStore = useAgentStore()
@@ -112,6 +114,7 @@
const { const {
inputValue, inputValue,
reportTags, reportTags,
optionTagKinds,
showPlaceholder, showPlaceholder,
setEditorElement, setEditorElement,
focusEditor, focusEditor,
@@ -156,6 +159,59 @@
clearImages clearImages
} = useInputImages(focusEditor) } = useInputImages(focusEditor)
const isReportSelected = computed(() => {
return reportTags.value.some((tag) => tag.parentNode)
})
const clearParameterValues = () => {
typeValue.value = ''
areaValue.value = ''
styleValue.value = ''
}
const handleToggleReportTag = () => {
const shouldSelectReport = !isReportSelected.value
toggleReportTag()
if (shouldSelectReport) {
clearParameterValues()
}
}
const getTranslatedOptionLabel = (options: OptionItem[], value: string) => {
const option = options.find((item) => item.value === value)
return option ? translate(option.label) : value
}
const getParameterTagLabel = (kind: OptionTagKind) => {
if (kind === 'type') {
return getTranslatedOptionLabel(typeOptions.value, typeValue.value)
}
if (kind === 'area') {
return getTranslatedOptionLabel(areaOptions.value, areaValue.value)
}
const option = styleOptions.value.find((item) => item.value === styleValue.value)
return option?.label || styleValue.value
}
const hasParameterValue = (kind: OptionTagKind) => {
if (kind === 'type') return Boolean(typeValue.value)
if (kind === 'area') return Boolean(areaValue.value)
return Boolean(styleValue.value)
}
const selectedParameterTags = computed<ParameterTag[]>(() => {
const currentKinds = optionTagKinds.value.filter(hasParameterValue)
const missingKinds = optionTagOrder.filter((kind) => {
return hasParameterValue(kind) && !currentKinds.includes(kind)
})
return [...currentKinds, ...missingKinds].map((kind) => ({
kind,
label: getParameterTagLabel(kind)
}))
})
watch(inputValue, () => { watch(inputValue, () => {
nextTick(() => { nextTick(() => {
autoResizeEditor() autoResizeEditor()
@@ -199,12 +255,14 @@
images: any[] images: any[]
tempImages: typeof uploadedImages.value tempImages: typeof uploadedImages.value
quoteList: string[] quoteList: string[]
parameterTags: ParameterTag[]
useReport?: boolean useReport?: boolean
} = { } = {
text: inputValue.value.trim(), text: inputValue.value.trim(),
images: imageUrlList, images: imageUrlList,
tempImages: uploadedImages.value, tempImages: uploadedImages.value,
quoteList: quoteList.value quoteList: quoteList.value,
parameterTags: selectedParameterTags.value
} }
if (reportTags.value.length > 0) { if (reportTags.value.length > 0) {
payload.useReport = true payload.useReport = true
@@ -238,6 +296,7 @@
tempImages: uploadedImages.value, tempImages: uploadedImages.value,
needSuggestion: false, needSuggestion: false,
quoteList: quoteList.value, quoteList: quoteList.value,
parameterTags: selectedParameterTags.value,
...params ...params
}) })

View File

@@ -171,10 +171,15 @@
column-gap: 0.8rem; column-gap: 0.8rem;
color: #0d0d0d; color: #0d0d0d;
&:hover {
cursor: pointer;
}
.option-tag-icon { .option-tag-icon {
width: 1.2rem; width: 1.2rem;
height: 1.2rem; height: 1.2rem;
flex-shrink: 0; flex-shrink: 0;
object-fit: contain;
} }
.option-tag-text { .option-tag-text {
@@ -185,10 +190,8 @@
white-space: nowrap; white-space: nowrap;
} }
.option-close { .option-tag-close {
width: 0.7rem; cursor: pointer;
height: 0.7rem;
margin-left: 0.2rem;
} }
} }

View File

@@ -24,6 +24,7 @@
v-if="!isAgentMode" v-if="!isAgentMode"
v-model="typeModel" v-model="typeModel"
:placeholder="typePlaceholder" :placeholder="typePlaceholder"
:disabled="parametersDisabled"
> >
<el-option <el-option
v-for="item in typeOptions" v-for="item in typeOptions"
@@ -37,6 +38,7 @@
v-if="!isAgentMode" v-if="!isAgentMode"
v-model="areaModel" v-model="areaModel"
:placeholder="areaPlaceholder" :placeholder="areaPlaceholder"
:disabled="parametersDisabled"
> >
<el-option <el-option
v-for="item in areaOptions" v-for="item in areaOptions"
@@ -54,12 +56,13 @@
:placeholder="stylePlaceholder" :placeholder="stylePlaceholder"
:title="styleTitle" :title="styleTitle"
:confirm-text="confirmText" :confirm-text="confirmText"
:disabled="parametersDisabled"
/> />
<SettingPopover <!-- <SettingPopover
v-model:options="settingOptionsModel" v-model:options="settingOptionsModel"
:title="settingTitle" :title="settingTitle"
:translate="translate" :translate="translate"
/> /> -->
</div> </div>
<div class="right"> <div class="right">
<div <div
@@ -109,11 +112,13 @@
settingTitle: string settingTitle: string
confirmText: string confirmText: string
createText: string createText: string
parametersDisabled?: boolean
translate: (key: string) => string translate: (key: string) => string
}>(), }>(),
{ {
isAgentMode: false, isAgentMode: false,
generating: false generating: false,
parametersDisabled: false
} }
) )
@@ -198,6 +203,13 @@
.el-select__icon { .el-select__icon {
color: #000; color: #000;
} }
&.is-disabled {
.el-select__placeholder,
.el-select__selected-item {
color: #c9c9c9;
}
}
} }
} }

View File

@@ -1,13 +1,15 @@
<template> <template>
<div class="fida-style-select-wrapper"> <div class="fida-style-select-wrapper" :class="{ 'is-disabled': disabled }">
<el-select <el-select
:model-value="modelValue" :model-value="modelValue"
:placeholder="placeholder" :placeholder="placeholder"
:disabled="disabled"
@focus="openStylePopup" @focus="openStylePopup"
/> />
<el-popover <el-popover
v-model:visible="stylePopupVisible" v-model:visible="stylePopupVisible"
:disabled="disabled"
placement="top" placement="top"
:width="342" :width="342"
:show-arrow="false" :show-arrow="false"
@@ -49,7 +51,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, watch } from 'vue'
import { getStyleImage } from '../style' import { getStyleImage } from '../style'
import type { OptionItem } from './types' import type { OptionItem } from './types'
@@ -60,6 +62,7 @@
placeholder: string placeholder: string
title: string title: string
confirmText: string confirmText: string
disabled?: boolean
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -69,12 +72,31 @@
const tempSelectedValue = ref('') const tempSelectedValue = ref('')
const stylePopupVisible = ref(false) const stylePopupVisible = ref(false)
watch(
() => props.modelValue,
(value) => {
tempSelectedValue.value = value
},
{ immediate: true }
)
watch(
() => props.disabled,
(disabled) => {
if (disabled) {
stylePopupVisible.value = false
}
}
)
const openStylePopup = () => { const openStylePopup = () => {
if (props.disabled) return
tempSelectedValue.value = props.modelValue tempSelectedValue.value = props.modelValue
stylePopupVisible.value = true stylePopupVisible.value = true
} }
const selectStyle = (value: string) => { const selectStyle = (value: string) => {
if (props.disabled) return
tempSelectedValue.value = value tempSelectedValue.value = value
confirmStyle() confirmStyle()
} }
@@ -111,6 +133,13 @@
.el-select__icon { .el-select__icon {
color: #000; color: #000;
} }
&.is-disabled {
.el-select__placeholder,
.el-select__selected-item {
color: #c9c9c9;
}
}
} }
} }
} }
@@ -124,6 +153,12 @@
z-index: 1; z-index: 1;
cursor: pointer; cursor: pointer;
} }
.is-disabled {
.fida-style-select-trigger {
cursor: not-allowed;
}
}
</style> </style>
<style lang="less"> <style lang="less">

View File

@@ -17,3 +17,8 @@ export interface UploadedImage {
export type PreviewImage = UploadedImage | string export type PreviewImage = UploadedImage | string
export type OptionTagKind = 'type' | 'area' | 'style' export type OptionTagKind = 'type' | 'area' | 'style'
export interface ParameterTag {
kind: OptionTagKind
label: string
}

View File

@@ -27,6 +27,7 @@ export function useInputEditor(options: UseInputEditorOptions) {
const inputValue = ref<string>('') const inputValue = ref<string>('')
const reportTags = ref<HTMLElement[]>([]) const reportTags = ref<HTMLElement[]>([])
const optionTags = ref<Partial<Record<OptionTagKind, HTMLElement>>>({}) const optionTags = ref<Partial<Record<OptionTagKind, HTMLElement>>>({})
const optionTagKinds = ref<OptionTagKind[]>([])
const reportPromptText = ref<Text | null>(null) const reportPromptText = ref<Text | null>(null)
let reportTypewriterTimeout: ReturnType<typeof setTimeout> | null = null let reportTypewriterTimeout: ReturnType<typeof setTimeout> | null = null
@@ -114,6 +115,18 @@ export function useInputEditor(options: UseInputEditorOptions) {
return StyleIcon return StyleIcon
} }
const refreshOptionTagKinds = () => {
const editor = editorRef.value
if (!editor) {
optionTagKinds.value = []
return
}
optionTagKinds.value = Array.from(editor.children)
.map((child) => (child as HTMLElement).dataset.optionTagKind as OptionTagKind)
.filter(Boolean)
}
const getTranslatedOptionLabel = (items: OptionItem[], value: string) => { const getTranslatedOptionLabel = (items: OptionItem[], value: string) => {
const option = items.find((item) => item.value === value) const option = items.find((item) => item.value === value)
return option ? options.t(option.label) : value return option ? options.t(option.label) : value
@@ -132,11 +145,13 @@ export function useInputEditor(options: UseInputEditorOptions) {
removeNodeWithNextSpacer(tag) removeNodeWithNextSpacer(tag)
} }
delete optionTags.value[kind] delete optionTags.value[kind]
refreshOptionTagKinds()
} }
const removeAllOptionTags = () => { const removeAllOptionTags = () => {
optionTagOrder.forEach(removeOptionTag) optionTagOrder.forEach(removeOptionTag)
optionTags.value = {} optionTags.value = {}
optionTagKinds.value = []
} }
const clearOptionTagValue = (kind: OptionTagKind) => { const clearOptionTagValue = (kind: OptionTagKind) => {
@@ -172,26 +187,33 @@ export function useInputEditor(options: UseInputEditorOptions) {
tag.contentEditable = 'false' tag.contentEditable = 'false'
tag.className = 'editor-tag option-tag flex-center' tag.className = 'editor-tag option-tag flex-center'
tag.dataset.optionTagKind = kind tag.dataset.optionTagKind = kind
const optionIcon = getOptionTagIcon(kind) as unknown as string
const icon = document.createElement('img') const icon = document.createElement('img')
icon.className = 'option-tag-icon' icon.className = 'option-tag-icon'
icon.src = getOptionTagIcon(kind) as unknown as string icon.src = optionIcon
icon.addEventListener('click', (ev) => {
ev.stopPropagation()
clearOptionTagValue(kind)
})
const textSpan = document.createElement('span') const textSpan = document.createElement('span')
textSpan.className = 'option-tag-text' textSpan.className = 'option-tag-text'
textSpan.innerText = label textSpan.innerText = label
const close = document.createElement('img') tag.addEventListener('mouseenter', () => {
close.className = 'close-icon option-close' icon.src = closeIcon as unknown as string
close.src = closeIcon as unknown as string icon.classList.add('option-tag-close')
close.addEventListener('click', (ev) => { tag.classList.add('is-hovered')
ev.stopPropagation() })
clearOptionTagValue(kind) tag.addEventListener('mouseleave', () => {
icon.src = optionIcon
icon.classList.remove('option-tag-close')
tag.classList.remove('is-hovered')
}) })
tag.appendChild(icon) tag.appendChild(icon)
tag.appendChild(textSpan) tag.appendChild(textSpan)
tag.appendChild(close)
return tag return tag
} }
@@ -230,6 +252,7 @@ export function useInputEditor(options: UseInputEditorOptions) {
} }
optionTags.value[kind] = tag optionTags.value[kind] = tag
refreshOptionTagKinds()
handleEditorInput() handleEditorInput()
} }
@@ -503,12 +526,14 @@ export function useInputEditor(options: UseInputEditorOptions) {
const resetOptionTags = () => { const resetOptionTags = () => {
optionTags.value = {} optionTags.value = {}
optionTagKinds.value = []
} }
return { return {
editorRef, editorRef,
inputValue, inputValue,
reportTags, reportTags,
optionTagKinds,
showPlaceholder, showPlaceholder,
setEditorElement, setEditorElement,
focusEditor, focusEditor,

View File

@@ -127,6 +127,7 @@
if (res) { if (res) {
userInfoStore.setToken(res) userInfoStore.setToken(res)
userInfoStore.setUserInfo({ userInfoStore.setUserInfo({
username: formData.name,
email: formData.email email: formData.email
}) })
router.push({ name: 'nuic' }) router.push({ name: 'nuic' })