feature:概念测试方案配置添加
This commit is contained in:
73
src/views/Concept/ConceptTesting.vue
Normal file
73
src/views/Concept/ConceptTesting.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="modalVisible"
|
||||
:maskClosable="false"
|
||||
:destroyOnClose="true"
|
||||
:closable="false"
|
||||
:footer="null"
|
||||
width="100%"
|
||||
wrapClassName="my-concept-full-modal"
|
||||
>
|
||||
<concept-layout :testType="testType" @closeConceptModal="closeModal" />
|
||||
</a-modal>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineExpose, ref, defineProps } from "vue";
|
||||
import ConceptLayout from "./components/ConceptLayout.vue";
|
||||
|
||||
import { Modal } from 'ant-design-vue'
|
||||
|
||||
const modalVisible = ref(false);
|
||||
|
||||
const testType = ref(1)
|
||||
const openModal = (type) => {
|
||||
if(type) {
|
||||
testType.value = type
|
||||
}
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
Modal.confirm({
|
||||
title: '确定退出?',
|
||||
content: '退出后当前方案配置无法恢复!',
|
||||
cancelText: '取 消',
|
||||
okText: '确 定',
|
||||
class: 'custom-modal custom-modal-title-confirm-notice',
|
||||
onOk: () => {
|
||||
modalVisible.value = false;
|
||||
|
||||
},
|
||||
onCancel: () => {}
|
||||
})
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
<style lang="scss">
|
||||
.my-concept-full-modal {
|
||||
.ant-modal {
|
||||
max-width: 100%;
|
||||
top: 0;
|
||||
padding-bottom: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.ant-modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh);
|
||||
}
|
||||
.ant-modal-body {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
10
src/views/Concept/api.js
Normal file
10
src/views/Concept/api.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
/* 新增方案配置 */
|
||||
export function postTemplates(sn,data) {
|
||||
return request({
|
||||
method: "POST",
|
||||
url: `/console/templates/${sn}`,
|
||||
data,
|
||||
});
|
||||
}
|
||||
603
src/views/Concept/components/ConceptConfig.vue
Normal file
603
src/views/Concept/components/ConceptConfig.vue
Normal file
@@ -0,0 +1,603 @@
|
||||
<template>
|
||||
<!-- 配置 -->
|
||||
<div class="survey-box">
|
||||
<div class="title">问卷配置</div>
|
||||
|
||||
<a-form ref="formRefSet" :model="ruleFormSet" :label-col="{ span: 4 }">
|
||||
<a-form-item label="测试版本" name="testType">
|
||||
<a-radio-group v-model:value="ruleFormSet.testType">
|
||||
<a-radio :value="item.id" v-for="item in testVersionList" :key="item.id" @click="onChangeTestType(item)">{{ item.name }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<StandardConceptConfig ref="standardConceptConfigRef"></StandardConceptConfig>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="target-box">
|
||||
<div class="target-box-head div-flex">
|
||||
<div class="title">
|
||||
<span class="title-span">指标配置</span>
|
||||
|
||||
<a-tooltip
|
||||
placement="top"
|
||||
:overlayStyle="{'width': '201px', 'border-radius': '6px'}"
|
||||
:getPopupContainer="(el) => el.parentNode">
|
||||
<i class="iconfont icon-xingzhuangjiehe2"></i>
|
||||
<template #title>
|
||||
<span style="font-weight: normal;font-size: 10px;">您在此处勾选拟调研的指标,每个指标对应一道问题,问卷将针对每个概念分别询问被访者相关问题</span>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
||||
<div v-if="ruleFormSet.testType === 1">
|
||||
<a-checkbox
|
||||
class="custom-checkbox"
|
||||
:indeterminate="isIndeterminate"
|
||||
:checked="isCheckedAll"
|
||||
@change="toggleCheckAll">
|
||||
全选
|
||||
</a-checkbox>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="target-box-content">
|
||||
<div v-if="ruleFormSet.testType === 1">
|
||||
<a-checkbox-group v-model:value="testOneSelectedValues">
|
||||
<div class="check-box">
|
||||
<div v-for="item in testOneData" :key="item.value" class="check-item">
|
||||
<a-checkbox class="custom-checkbox my-checkbox" :value="item.value" :disabled="item.disabled">
|
||||
<span :class="{'check-label': item.disabled}">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
</a-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
|
||||
<div v-if="ruleFormSet.testType === 2">
|
||||
<a-checkbox-group v-model:value="testTwoSelectedValues">
|
||||
<div class="check-box">
|
||||
<div v-for="item in testTwoData" :key="item.value" class="check-item">
|
||||
<a-checkbox class="custom-checkbox my-checkbox" :value="item.value" :disabled="item.disabled">
|
||||
<span :class="{'check-label': item.disabled}">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
</a-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
|
||||
<div v-if="ruleFormSet.testType === 3">
|
||||
<a-checkbox-group v-model:value="testThreeSelectedValues">
|
||||
<div class="check-box">
|
||||
<div v-for="item in testThreeData" :key="item.value" class="check-item check-item-three">
|
||||
<a-checkbox class="custom-checkbox my-checkbox" :value="item.value" :disabled="item.disabled">
|
||||
<span :class="{'check-label': item.disabled}">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
</a-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="concept-box">
|
||||
<div class="concept-box-head div-flex">
|
||||
<div class="title">
|
||||
<span class="title-span">概念配置</span>
|
||||
|
||||
<a-tooltip
|
||||
placement="top"
|
||||
:overlayStyle="{'width': '201px', 'border-radius': '6px'}"
|
||||
:getPopupContainer="(el) => el.parentNode">
|
||||
<i class="iconfont icon-xingzhuangjiehe2"></i>
|
||||
<template #title>
|
||||
<span style="font-weight: normal;font-size: 10px;">您在此处配置拟调研的概念,问卷将针对每个概念分别询问被访者相关问题</span>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
||||
<div v-if="ruleFormSet.testType !== 3 && isNoAdd">
|
||||
<a-button class="custom-button" type="primary" @click="onAddConcept">
|
||||
<PlusOutlined/> 新增概念
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="concept-box-content">
|
||||
<!-- 标准版 -->
|
||||
<draggable v-if="ruleFormSet.testType === 1"
|
||||
v-model="concepts"
|
||||
item-key="key"
|
||||
animation="300"
|
||||
:scroll="true"
|
||||
class="drag-box"
|
||||
handle=".moveConcept"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="concept-wrapper" :class="{'mar-7': index % 2 === 0, 'mal-7': index % 2 !== 0}">
|
||||
<ConceptSetItem
|
||||
v-model="element.data"
|
||||
:key="element.key"
|
||||
:index="index"
|
||||
:allItem="concepts"
|
||||
@changeType="onChangeType"
|
||||
>
|
||||
<div class="actions">
|
||||
<i class="iconfont defaultIcon moveConcept"></i>
|
||||
|
||||
<i class="iconfont filterDelIcon" @click="deleteConcept(element.key)"></i>
|
||||
|
||||
</div>
|
||||
</ConceptSetItem>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<!-- 快测版 -->
|
||||
<draggable v-if="ruleFormSet.testType === 2"
|
||||
v-model="testTwoConcepts"
|
||||
item-key="key"
|
||||
animation="300"
|
||||
:scroll="true"
|
||||
class="drag-box"
|
||||
handle=".moveTwoConcept"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="concept-wrapper" :class="{'mar-7': index % 2 === 0, 'mal-7': index % 2 !== 0}">
|
||||
<ConceptSetItem
|
||||
v-model="element.data"
|
||||
:key="element.key"
|
||||
:index="index"
|
||||
:allItem="testTwoConcepts"
|
||||
>
|
||||
<div class="actions">
|
||||
<i class="iconfont defaultIcon moveTwoConcept"></i>
|
||||
|
||||
<i class="iconfont filterDelIcon" @click="deleteConcept(element.key)"></i>
|
||||
|
||||
</div>
|
||||
</ConceptSetItem>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<!-- 配对版 -->
|
||||
<draggable v-if="ruleFormSet.testType === 3"
|
||||
v-model="testThreeConcepts"
|
||||
item-key="key"
|
||||
animation="300"
|
||||
:scroll="true"
|
||||
class="drag-box"
|
||||
handle=".moveThreeConcept"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="concept-wrapper" :class="{'mar-7': index % 2 === 0, 'mal-7': index % 2 !== 0}">
|
||||
<ConceptSetItem
|
||||
v-model="element.data"
|
||||
:key="element.key"
|
||||
:index="index"
|
||||
>
|
||||
<div class="actions">
|
||||
<i class="iconfont defaultIcon moveThreeConcept"></i>
|
||||
|
||||
<!-- <i class="iconfont filterDelIcon" @click="deleteConcept(element.key)"></i> -->
|
||||
|
||||
</div>
|
||||
</ConceptSetItem>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<!-- <div
|
||||
v-for="(concept, index) in concepts"
|
||||
:key="concept.key"
|
||||
class="concept-wrapper"
|
||||
:class="{'mar-7': index % 2 === 0, 'mal-7': index % 2 !== 0}"
|
||||
>
|
||||
<ConceptSetItem
|
||||
v-model="concept.data"
|
||||
:key="concept.key"
|
||||
:index="index"
|
||||
>
|
||||
<div class="actions">
|
||||
<i class="iconfont defaultIcon"></i>
|
||||
|
||||
<i class="iconfont filterDelIcon" @click="deleteConcept(concept.key)"></i>
|
||||
|
||||
</div>
|
||||
</ConceptSetItem>
|
||||
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineEmits, ref, reactive, onBeforeMount, watch, computed } from 'vue'
|
||||
|
||||
import StandardConceptConfig from './StandardConceptConfig.vue'
|
||||
|
||||
import conceptData from '../json/concept.json'
|
||||
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
import ConceptSetItem from './ConceptSetItem.vue'
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import draggable from "vuedraggable";
|
||||
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
const emits = defineEmits(["changeTestType", "submitInfo"]);
|
||||
|
||||
const props = defineProps(["testType"]);
|
||||
|
||||
const formRefSet = ref();
|
||||
|
||||
const ruleFormSet = reactive({
|
||||
testType: props.testType || 1,
|
||||
})
|
||||
|
||||
/** 测试版本 */
|
||||
const testVersionList = [
|
||||
{ id: 1, name: '标准版' },
|
||||
{ id: 2, name: '快测版' },
|
||||
{ id: 3, name: '配对版' },
|
||||
]
|
||||
|
||||
/** 标准版 */
|
||||
const testOneData = ref(conceptData.testOneCheckInfo)
|
||||
|
||||
const testOneSelectedValues = ref(testOneData.value.filter(item => item.disabled).map(item => item.value));
|
||||
|
||||
const isCheckedAll = computed(() => {
|
||||
return testOneSelectedValues.value.length === testOneData.value.length;
|
||||
});
|
||||
|
||||
const isIndeterminate = ref(true);
|
||||
|
||||
watch(testOneSelectedValues, () => {
|
||||
const selectedCount = testOneSelectedValues.value.length;
|
||||
const totalCount = testOneData.value.length;
|
||||
const disabledCount = testOneData.value.filter(item => item.disabled).length;
|
||||
|
||||
if(selectedCount === totalCount) {
|
||||
isIndeterminate.value = false;
|
||||
}else{
|
||||
isIndeterminate.value = true;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
const toggleCheckAll = () => {
|
||||
if (isCheckedAll.value) {
|
||||
// 如果已全选,则取消选中所有非disabled项
|
||||
testOneSelectedValues.value = testOneData.value.filter(item => item.disabled).map(item => item.value);
|
||||
} else {
|
||||
// 否则选中所有非disabled项
|
||||
testOneSelectedValues.value = testOneData.value.map(item => item.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 概念配置
|
||||
const concepts = ref([
|
||||
{ key: uuidv4(), data: { concept_type: 0, concept_name: '', concept_url: '' } },
|
||||
{ key: uuidv4(), data: { concept_type: 1, concept_name: '', concept_url: '' } }
|
||||
]);
|
||||
|
||||
const isNoAdd = computed(() => {
|
||||
if(ruleFormSet.testType === 1 && concepts.value.length === 8) {
|
||||
return false
|
||||
}else if(ruleFormSet.testType === 2 && testTwoConcepts.value.length === 8) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// 新增概念
|
||||
const onAddConcept = () => {
|
||||
let item = { key: uuidv4(), data: { concept_type: 1, concept_name: '', concept_url: '' } }
|
||||
|
||||
if(ruleFormSet.testType === 1) {
|
||||
concepts.value.push(item);
|
||||
}else if(ruleFormSet.testType === 2) {
|
||||
testTwoConcepts.value.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
function deleteConcept(id) {
|
||||
if(ruleFormSet.testType === 1) {
|
||||
let checkList = concepts.value.filter(concept => concept.key !== id);
|
||||
|
||||
if(checkConceptType(checkList)) {
|
||||
concepts.value = concepts.value.filter(concept => concept.key !== id);
|
||||
}
|
||||
}else if(ruleFormSet.testType === 2) {
|
||||
let checkList = testTwoConcepts.value.filter(concept => concept.key !== id);
|
||||
|
||||
if(checkConceptType(checkList)) {
|
||||
testTwoConcepts.value = testTwoConcepts.value.filter(concept => concept.key !== id);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** 快测版 */
|
||||
const testTwoData = ref(conceptData.testTwoCheckInfo)
|
||||
|
||||
const testTwoSelectedValues = ref(testTwoData.value.filter(item => item.disabled).map(item => item.value));
|
||||
|
||||
// 标准版-概念配置
|
||||
const testTwoConcepts = ref([
|
||||
{ key: uuidv4(), data: { concept_type: 0, concept_name: '', concept_url: '' } },
|
||||
{ key: uuidv4(), data: { concept_type: 1, concept_name: '', concept_url: '' } }
|
||||
]);
|
||||
|
||||
/** 配对版 */
|
||||
const testThreeData = ref(conceptData.testThreeCheckInfo)
|
||||
|
||||
const testThreeSelectedValues = ref(testThreeData.value.filter(item => item.disabled).map(item => item.value));
|
||||
|
||||
// 概念配置
|
||||
const testThreeConcepts = ref([
|
||||
{ key: uuidv4(), data: { noType: true, concept_name: '', concept_url: '' } },
|
||||
{ key: uuidv4(), data: { noType: true, concept_name: '', concept_url: '' } }
|
||||
]);
|
||||
|
||||
onBeforeMount(() => {
|
||||
})
|
||||
|
||||
// 修改测试版本
|
||||
const onChangeTestType = (val) => {
|
||||
emits('changeTestType', val)
|
||||
}
|
||||
|
||||
|
||||
const standardConceptConfigRef = ref()
|
||||
// 方案配置数据
|
||||
const getConceptInfo = () => {
|
||||
|
||||
// 必填问卷名称
|
||||
standardConceptConfigRef.value.formRef.validate()
|
||||
.then(() => {
|
||||
let params = {
|
||||
...standardConceptConfigRef.value.ruleForm
|
||||
}
|
||||
|
||||
if(ruleFormSet.testType === 1) {
|
||||
console.log('testOneData', testOneData.value);
|
||||
console.log('testOneSelectedValues', testOneSelectedValues.value);
|
||||
|
||||
const enabledItems = testOneData.value.filter(item => !item.disabled);
|
||||
|
||||
const result = enabledItems.map(item => {
|
||||
const matchedField = testOneSelectedValues.value.find(field => item.value.includes(field));
|
||||
if (matchedField) {
|
||||
return { type: matchedField, is_select: 1 };
|
||||
}
|
||||
return null;
|
||||
}).filter(item => item !== null);
|
||||
|
||||
console.log('result', result);
|
||||
params.concept_indexes = result;
|
||||
params.concepts = concepts.value.map(item => item.data);
|
||||
}
|
||||
|
||||
if(ruleFormSet.testType === 2) {
|
||||
console.log('testTwoData', testTwoData.value);
|
||||
console.log('testTwoSelectedValues', testTwoSelectedValues.value);
|
||||
|
||||
const enabledItems = testTwoData.value.filter(item => !item.disabled);
|
||||
|
||||
const result = enabledItems.map(item => {
|
||||
const matchedField = testTwoSelectedValues.value.find(field => item.value.includes(field));
|
||||
if (matchedField) {
|
||||
return { type: matchedField, is_select: 1 };
|
||||
}
|
||||
return null;
|
||||
}).filter(item => item !== null);
|
||||
|
||||
console.log('result', result);
|
||||
params.concept_indexes = result;
|
||||
params.concepts = testTwoConcepts.value.map(item => item.data);
|
||||
}
|
||||
|
||||
if(ruleFormSet.testType === 3) {
|
||||
|
||||
params.concepts = testThreeConcepts.value.map(item => item.data);
|
||||
}
|
||||
|
||||
console.log('params', params);
|
||||
emits('submitInfo', params)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("error", error);
|
||||
});
|
||||
}
|
||||
|
||||
// 修改概念类型
|
||||
const onChangeType = (typeVal) => {
|
||||
console.log('typeVal', typeVal);
|
||||
console.log('ruleFormSet.testType', ruleFormSet.testType);
|
||||
let checkList = []
|
||||
if(ruleFormSet.testType === 1) {
|
||||
checkList = concepts.value
|
||||
console.log('checkList修改前', checkList);
|
||||
|
||||
checkList[typeVal.index].data.concept_type = typeVal.type
|
||||
console.log('checkList[typeVal.index]', checkList[typeVal.index].data);
|
||||
|
||||
console.log('checkList修改后', checkList);
|
||||
|
||||
|
||||
if(checkConceptType(checkList)) {
|
||||
concepts.value[typeVal.index].data.concept_type = typeVal.type
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 判断 concept_type 是否至少有一个 0 和一个 1
|
||||
function checkConceptType(arr) {
|
||||
if(arr.filter(item => item.data.noType).length > 0){
|
||||
return true
|
||||
}
|
||||
|
||||
const type0Count = arr.filter(item => item.data.concept_type === 0).length;
|
||||
console.log('type0Count', arr);
|
||||
|
||||
const type1Count = arr.filter(item => item.data.concept_type === 1).length;
|
||||
|
||||
// 判断是否只有一个 concept_type 为 0,且只有一个 concept_type 为 1
|
||||
if (type0Count >= 1 && type1Count >= 1) {
|
||||
return true;
|
||||
}else if(type0Count < 1){
|
||||
message.error('至少配置一个标杆概念')
|
||||
return false;
|
||||
}else if(type1Count < 1) {
|
||||
message.error('至少配置一个新品概念')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getConceptInfo
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.line {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #E8E8E8;
|
||||
}
|
||||
|
||||
.target-box {
|
||||
margin-top: 18px;
|
||||
margin-bottom: 18px;
|
||||
:deep(.ant-tooltip-inner) {
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
color: #70B936;
|
||||
margin-left: 9px;
|
||||
}
|
||||
|
||||
.title-span {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.div-flex {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.check-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.check-item {
|
||||
width: 116px;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
:deep(.ant-checkbox-wrapper) {
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.check-item-three {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.concept-box {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.check-label {
|
||||
|
||||
&::after {
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
color: #ff4d4f;
|
||||
font-size: 12px;
|
||||
content: '*';
|
||||
}
|
||||
}
|
||||
|
||||
.my-checkbox {
|
||||
:deep(.ant-checkbox-disabled .ant-checkbox-inner) {
|
||||
background-color: #D5EBC3 !important;
|
||||
border-color: #D5EBC3 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.concept-box-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.drag-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.concept-wrapper {
|
||||
width: calc(50% - 7px);
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.mar-7 {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.mal-7 {
|
||||
margin-left: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
||||
.defaultIcon {
|
||||
cursor: pointer;
|
||||
color: #dadada;
|
||||
font-size: 22px;
|
||||
transition: all .3s ease-in-out;
|
||||
&:hover{
|
||||
color: #70b936;
|
||||
}
|
||||
}
|
||||
|
||||
.filterDelIcon{
|
||||
cursor: pointer;
|
||||
color: #dadada;
|
||||
margin-left: 12px;
|
||||
font-size: 22px;
|
||||
transition: all .3s ease-in-out;
|
||||
&:hover{
|
||||
color: #70b936;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
350
src/views/Concept/components/ConceptLayout.vue
Normal file
350
src/views/Concept/components/ConceptLayout.vue
Normal file
@@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<div class="layout">
|
||||
<!-- 头部导航栏 -->
|
||||
<div class="header">
|
||||
<div @click="quitConcept" class="left">
|
||||
<i class="icon iconfont icon-fanhui"></i>
|
||||
<span>退出方案配置</span>
|
||||
</div>
|
||||
<div class="action-container">
|
||||
<a-button class="custom-button" type="primary" @click="saveConcept">保存方案</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-box">
|
||||
<!-- 配置 -->
|
||||
<div class="content scrollbar">
|
||||
|
||||
<ConceptConfig ref="conceptConfigRef" :testType="testType" @changeTestType="onChangeTestType" @submitInfo="onSubmit"></ConceptConfig>
|
||||
|
||||
</div>
|
||||
<!-- 模版详情 -->
|
||||
<div class="content scrollbar">
|
||||
<div class="template-box">
|
||||
<div class="title">模板详情</div>
|
||||
<ConceptTemplate :type="testTypeInfo.id"></ConceptTemplate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineEmits, ref } from 'vue'
|
||||
|
||||
import ConceptTemplate from './ConceptTemplate.vue'
|
||||
import ConceptConfig from './ConceptConfig.vue'
|
||||
|
||||
import { Modal, message } from 'ant-design-vue'
|
||||
|
||||
import { postTemplates } from '../api'
|
||||
|
||||
const props = defineProps(["testType"]);
|
||||
|
||||
const emits = defineEmits(["closeConceptModal"]);
|
||||
|
||||
const quitConcept = () => {
|
||||
emits('closeConceptModal')
|
||||
}
|
||||
|
||||
const conceptConfigRef = ref()
|
||||
|
||||
// 保存方案接口
|
||||
const onSubmit = async(params) => {
|
||||
console.log('提交 params', params);
|
||||
if(validateConceptNames(params.concepts) && checkConceptType(params.concepts)) {
|
||||
Modal.confirm({
|
||||
title: '确定保存?',
|
||||
content: '保存方案后无法编辑,请您再次确认已完成最终配置。',
|
||||
cancelText: '取 消',
|
||||
okText: '确 定',
|
||||
class: 'custom-modal custom-modal-title-confirm-notice',
|
||||
onOk: async() => {
|
||||
return
|
||||
|
||||
const res = await postTemplates(sn, params)
|
||||
|
||||
console.log('res', res);
|
||||
if(res.code === 200) {
|
||||
console.log('成功了');
|
||||
|
||||
|
||||
// router.push({
|
||||
// path: "/survey/planet/design",
|
||||
// query: { sn: data.sn },
|
||||
// });
|
||||
}
|
||||
},
|
||||
onCancel: () => {}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 判断 concept_type 是否至少有一个 0 和一个 1
|
||||
function checkConceptType(arr) {
|
||||
if(arr.filter(item => item.noType).length > 0){
|
||||
return true
|
||||
}
|
||||
|
||||
const type0Count = arr.filter(item => item.concept_type === 0).length;
|
||||
const type1Count = arr.filter(item => item.concept_type === 1).length;
|
||||
|
||||
// 判断是否只有一个 concept_type 为 0,且只有一个 concept_type 为 1
|
||||
if (type0Count >= 1 && type1Count >= 1) {
|
||||
return true;
|
||||
}else if(type0Count < 1){
|
||||
message.error('至少配置一个标杆概念')
|
||||
return false;
|
||||
}else if(type1Count < 1) {
|
||||
message.error('至少配置一个新品概念')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查 concept_name 是否为空,并且检查重复的名称
|
||||
function validateConceptNames(arr) {
|
||||
const names = arr.map(item => item.concept_name);
|
||||
|
||||
// 检查是否有空名称
|
||||
const emptyNameExists = names.some(name => name.trim() === "");
|
||||
if (emptyNameExists) {
|
||||
message.error("概念名称不可为空,请检查后重试!")
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否有重复名称
|
||||
const nameCounts = names.reduce((acc, name) => {
|
||||
acc[name] = (acc[name] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const duplicateNames = Object.keys(nameCounts).filter(name => nameCounts[name] > 1);
|
||||
if (duplicateNames.length > 0) {
|
||||
// return `重复名称:${duplicateNames.join(', ')}。请重新填写。`;
|
||||
message.error('概念名称不可重复,请检查后重试!')
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('名称验证通过。');
|
||||
return true;
|
||||
}
|
||||
|
||||
// 保存方案 按钮
|
||||
const saveConcept = () => {
|
||||
|
||||
// const params = {
|
||||
// // ...
|
||||
// }
|
||||
// console.log('保存方案', params);
|
||||
conceptConfigRef.value.getConceptInfo()
|
||||
|
||||
}
|
||||
|
||||
const testTypeInfo = ref({ id: 1, name: '标准版' })
|
||||
const onChangeTestType = (val) => {
|
||||
console.log('val', val);
|
||||
|
||||
testTypeInfo.value = val
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout {
|
||||
min-width: 1200px;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.header {
|
||||
height: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px 0 24px;
|
||||
background: #fff;
|
||||
box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.left {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.iconfont {
|
||||
font-size: 20px;
|
||||
color: #434343;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
color: #434343;
|
||||
line-height: 24px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
.action-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.content-box {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.content{
|
||||
padding: 22px;
|
||||
:deep(.title) {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
line-height: 24px;
|
||||
position: relative;
|
||||
padding-left: 15px;
|
||||
margin-bottom: 18px;
|
||||
&::after {
|
||||
content: "";
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background: #70b936;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.content {
|
||||
width: calc(50% - 12px);
|
||||
padding: 0 24px;
|
||||
height: calc(100vh - 150px);
|
||||
overflow: auto;
|
||||
overflow: overlay;
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
transition: all 0.2s;
|
||||
max-width: 1280px;
|
||||
|
||||
.content-header {
|
||||
height: 70px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.ch-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
|
||||
span {
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
line-height: 18px;
|
||||
margin-left: 6px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.tab {
|
||||
width: 94px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 1px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border: 1px solid #e1e1e1;
|
||||
cursor: pointer;
|
||||
|
||||
.iconfont {
|
||||
font-size: 20px;
|
||||
margin-right: 4px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
.phone-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-l {
|
||||
border-radius: 4px 0px 0px 4px;
|
||||
}
|
||||
|
||||
.tab-r {
|
||||
border-radius: 0px 4px 4px 0px;
|
||||
}
|
||||
|
||||
.active {
|
||||
border-color: #70b936;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
|
||||
span,
|
||||
.iconfont {
|
||||
color: #70b936;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.phone {
|
||||
margin: 0 auto;
|
||||
|
||||
:deep(.ant-spin-nested-loading) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-spin-container) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.open {
|
||||
top: calc(50% - 127px);
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 254px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #bfbfbf;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.open-left {
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.open-right {
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.open:hover {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.open:active {
|
||||
color: #70b936;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
301
src/views/Concept/components/ConceptSetItem.vue
Normal file
301
src/views/Concept/components/ConceptSetItem.vue
Normal file
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<div class="concept-item">
|
||||
<div class="item-btn">
|
||||
<div class="tab-header">
|
||||
<div v-if="!conceptItem?.noType" class="tab-btn tab-btn-one" :class="{'btn-actived': conceptItem.concept_type === 0}" @click="onChangeType(0)">标杆概念</div>
|
||||
<div v-if="!conceptItem?.noType" class="tab-btn tab-btn-two" :class="{'btn-actived': conceptItem.concept_type === 1}" @click="onChangeType(1)">新品概念</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="concept-name">
|
||||
<a-input
|
||||
style="border-radius: 4px;width: 100%"
|
||||
v-model:value="conceptItem.concept_name"
|
||||
placeholder="请输入概念名称"
|
||||
:maxlength="30"
|
||||
showCount
|
||||
@blur="onBlur"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="suffix">
|
||||
{{ `${conceptItem?.concept_name?.length} / 30` }}
|
||||
</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="uploadLoading">
|
||||
|
||||
<a-upload-dragger
|
||||
class="my-upload-dragger"
|
||||
:showUploadList="false"
|
||||
:customRequest="uploadFile"
|
||||
@change="handleChange($event, item, index)"
|
||||
accept="image/*"
|
||||
>
|
||||
<div class="packing-upload upload-btn">
|
||||
<img class="u-icon" src="@/assets/img/upload_img.png" />
|
||||
<div class="u-title">上传图片</div>
|
||||
<div class="u-desc">不能超过2M</div>
|
||||
<div class="u-desc">不能超过2M 建议尺寸300×200px</div>
|
||||
</div>
|
||||
</a-upload-dragger>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineEmits, defineProps, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import CommonApi from "@/api/common.js";
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const props = defineProps({
|
||||
index: Number,
|
||||
modelValue: Object,
|
||||
allItem: Array,
|
||||
});
|
||||
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
|
||||
const conceptItem = ref({ ...props.modelValue })
|
||||
|
||||
watch(conceptItem, (newVal) => {
|
||||
emits('update:modelValue', newVal);
|
||||
}, { deep: true });
|
||||
|
||||
const uploadLoading = ref(false);
|
||||
|
||||
// 自定义上传文件
|
||||
async function uploadFile(e) {
|
||||
uploadLoading.value = true;
|
||||
const { file, onSuccess } = e;
|
||||
const fileName = file.name.replaceAll(" ", "");
|
||||
// 校验文件类型
|
||||
if (file.type.split("/")[0] !== "image") {
|
||||
uploadLoading.value = false;
|
||||
return message.error("请上传图片");
|
||||
}
|
||||
// 不能超过2M
|
||||
if (file.size > 2 * 1024 * 1024) {
|
||||
uploadLoading.value = false;
|
||||
return message.error("图片不能超过2M");
|
||||
}
|
||||
// 上传文件到oss
|
||||
const result = await CommonApi.cosUpload(
|
||||
file,
|
||||
`packing/imgs/${new Date().getTime()}_${Math.floor(Math.random() * 1000)}_${fileName}`
|
||||
);
|
||||
console.log('result', result);
|
||||
|
||||
if(result?.url) {
|
||||
conceptItem.value.concept_url = result.url
|
||||
onSuccess({ url: result.url }, file);
|
||||
}else{
|
||||
message.error('上传失败')
|
||||
}
|
||||
uploadLoading.value = false;
|
||||
// 上传成功
|
||||
}
|
||||
|
||||
// 上传文件改变时的状态
|
||||
function handleChange(info, index) {
|
||||
const status = info.file.status;
|
||||
|
||||
if (status === 'uploading') {
|
||||
uploadLoading.value = true;
|
||||
}
|
||||
if (status === "done") {
|
||||
message.success(`${info.file.name} 上传成功.`);
|
||||
uploadLoading.value = false;
|
||||
} else if (status === "error") {
|
||||
message.error(`${info.file.name} 上传失败.`);
|
||||
uploadLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 修改概念类型
|
||||
const onChangeType = (type) => {
|
||||
|
||||
let oldType = conceptItem.value.concept_type
|
||||
|
||||
conceptItem.value.concept_type = type
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
if(!checkConceptType(props.allItem)) {
|
||||
|
||||
setTimeout(() => {
|
||||
conceptItem.value.concept_type = oldType
|
||||
}, 100)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 判断 concept_type 是否至少有一个 0 和一个 1
|
||||
function checkConceptType(arr) {
|
||||
if(arr.filter(item => item.data.noType).length > 0){
|
||||
return true
|
||||
}
|
||||
const type0Count = arr.filter(item => item.data.concept_type === 0).length;
|
||||
|
||||
const type1Count = arr.filter(item => item.data.concept_type === 1).length;
|
||||
|
||||
if (type0Count >= 1 && type1Count >= 1) {
|
||||
return true;
|
||||
}else if(type0Count < 1){
|
||||
message.error('至少配置一个标杆概念')
|
||||
return false;
|
||||
}else if(type1Count < 1) {
|
||||
message.error('至少配置一个新品概念')
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 失焦校验
|
||||
const onBlur = () => {
|
||||
console.log('proos', props.allItem);
|
||||
// return
|
||||
const names = props.allItem.map(item => item.data.concept_name);
|
||||
|
||||
// 检查是否有空名称
|
||||
const emptyNameExists = names.some(name => name.trim() === "");
|
||||
if (emptyNameExists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否有重复名称
|
||||
const nameCounts = names.reduce((acc, name) => {
|
||||
acc[name] = (acc[name] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const duplicateNames = Object.keys(nameCounts).filter(name => nameCounts[name] > 1);
|
||||
if (duplicateNames.length > 0) {
|
||||
// return `重复名称:${duplicateNames.join(', ')}。请重新填写。`;
|
||||
message.error('概念名称不可重复,请检查后重试!')
|
||||
return false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.concept-item {
|
||||
padding: 14px;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
background: #F9F9F9;
|
||||
|
||||
.item-btn {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.concept-name {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// .actions {
|
||||
|
||||
// .defaultIcon {
|
||||
// cursor: pointer;
|
||||
// color: #dadada;
|
||||
// font-size: 22px;
|
||||
// transition: all .3s ease-in-out;
|
||||
// &:hover{
|
||||
// color: #70b936;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .filterDelIcon{
|
||||
// cursor: pointer;
|
||||
// color: #dadada;
|
||||
// margin-left: 12px;
|
||||
// font-size: 22px;
|
||||
// transition: all .3s ease-in-out;
|
||||
// &:hover{
|
||||
// color: #70b936;
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
.tab-header {
|
||||
display: flex;
|
||||
|
||||
.tab-btn {
|
||||
width: 68px;
|
||||
height: 26px;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
line-height: 26px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tab-btn-one {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.tab-btn-two {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.btn-actived {
|
||||
color: #FFFFFF;
|
||||
background: #70B936;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.packing-upload {
|
||||
// width: 176px;
|
||||
// height: 176px;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
// background: #f4faef;
|
||||
|
||||
img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.u-title {
|
||||
font-size: 14px;
|
||||
// font-weight: 500;
|
||||
color: #70b936;
|
||||
line-height: 20px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.u-desc {
|
||||
font-size: 12px;
|
||||
color: #70b936;
|
||||
line-height: 18px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.my-upload-dragger {
|
||||
:deep(.ant-upload) {
|
||||
border-width: 0px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
118
src/views/Concept/components/ConceptTemplate.vue
Normal file
118
src/views/Concept/components/ConceptTemplate.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="template-content-box">
|
||||
<div class="head-title">
|
||||
<div class="head-title-one">液态奶产品研究标准化问卷-概念测试(内测{{ typeTitle }})</div>
|
||||
<div class="head-title-two">【样本量要求≧60】</div>
|
||||
</div>
|
||||
<div class="question-content" v-for="(item, index) in questionInfoList" :key="index">
|
||||
<div class="question-title" v-if="item?.title">{{ item.title }}</div>
|
||||
|
||||
<div class="mb-8" v-if="item?.topic">{{ item.topic }}</div>
|
||||
|
||||
<div class="question-tip mb-8" v-if="item?.tip">{{ item.tip }}</div>
|
||||
|
||||
<div class="mb-8" v-for="it in item.ans" :key="it">{{ it }}</div>
|
||||
|
||||
<div class="div-center mb-8" v-if="item?.center">{{ item.center }}</div>
|
||||
|
||||
<div class="div-table" v-if="item?.table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(header, index) in item.table.headers" :key="index">{{ header }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, rowIndex) in item.table.rows" :key="rowIndex">
|
||||
<td v-for="(header, colIndex) in item.table.headers" :key="colIndex">{{ row[item.table.headerMap[header]] }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, defineProps } from 'vue'
|
||||
|
||||
import { questionInfoOne, questionInfoTwo, questionInfoThree } from '@/views/Concept/json/concept.json'
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
})
|
||||
|
||||
const testType = computed(() => props.type)
|
||||
|
||||
const typeTitle = computed(() => testType.value === 1 ? '标准版' : testType.value === 2 ? '快测版' : testType.value === 3 ? '配对版' : '')
|
||||
|
||||
const questionInfoList = computed(() => testType.value === 1 ? questionInfoOne : testType.value === 2 ? questionInfoTwo : questionInfoThree)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.template-content-box {
|
||||
.head-title {
|
||||
text-align: center;
|
||||
.head-title-one {
|
||||
font-size: 20px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.head-title-two {
|
||||
font-size: 20px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.question-content {
|
||||
.question-title {
|
||||
font-size: 16px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.question-tip {
|
||||
color: #70B936;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.mb-8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.div-center {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.div-table {
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f8f8f8;
|
||||
font-weight: bold;
|
||||
// color: red;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td {
|
||||
// width: 33%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
433
src/views/Concept/components/StandardConceptConfig.vue
Normal file
433
src/views/Concept/components/StandardConceptConfig.vue
Normal file
@@ -0,0 +1,433 @@
|
||||
<template>
|
||||
<a-form ref="formRef" :model="ruleForm" :rules="rules" :label-col="{ span: 4 }">
|
||||
<a-form-item label="问卷名称" name="project_name">
|
||||
<a-input
|
||||
style="border-radius: 4px;"
|
||||
v-model:value="ruleForm.project_name"
|
||||
placeholder="请输入问卷名称"
|
||||
:maxlength="30"
|
||||
showCount
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="suffix">
|
||||
{{ `${ruleForm.project_name.length} / 30` }}
|
||||
</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="问卷场景" name="scene_code_info" v-if="isShow">
|
||||
<a-select
|
||||
disabled
|
||||
v-model:value="ruleForm.scene_code_info"
|
||||
style="width: 100%;border-radius: 4px;"
|
||||
placeholder="请选择场景"
|
||||
@change="handleSceneChange"
|
||||
class="custom-select show-select"
|
||||
:dropdownStyle="{zIndex: 10000}"
|
||||
>
|
||||
<a-select-option
|
||||
:value="`${item.code}`"
|
||||
:label="item.title"
|
||||
v-for="item in scenesList"
|
||||
:key="`${item.code}`"
|
||||
>
|
||||
{{item.parentTitle}}-{{ item.title }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="问卷标签" name="tags" v-if="isShow">
|
||||
<a-select
|
||||
v-model:value="ruleForm.tags"
|
||||
style="width: 100%;border-radius: 4px;"
|
||||
mode="multiple"
|
||||
placeholder="搜索或新建标签"
|
||||
@change="handleChange"
|
||||
:filterOption="filterOption"
|
||||
class="custom-select show-select"
|
||||
:dropdownStyle="{zIndex: 10000}"
|
||||
>
|
||||
<a-select-option
|
||||
:value="item.id"
|
||||
:label="item.title"
|
||||
v-for="item in tagsList"
|
||||
:key="item.id"
|
||||
>
|
||||
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<span :style="countColor(item.color)" :title="item.title">{{ item.title }}</span>
|
||||
<span class="icon" v-show="isAdmin">
|
||||
<EditOutlined class="edit" @click.stop="edit(item)" />
|
||||
<DeleteOutlined class="del" @click.stop="del(item.id)" />
|
||||
</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<template #dropdownRender="{ menuNode: menu }" v-if="isAdmin">
|
||||
<v-nodes :vnodes="menu" />
|
||||
<a-divider style="margin: 4px 0" />
|
||||
<div
|
||||
style="padding: 2px 8px; cursor: pointer; color:#70b936"
|
||||
@mousedown="(e) => e.preventDefault()"
|
||||
@click="onAddTag"
|
||||
>
|
||||
<plus-outlined />
|
||||
新建标签
|
||||
</div>
|
||||
</template>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="问卷简介" name="remarks" v-if="isShow">
|
||||
<a-input
|
||||
style="border-radius: 4px;"
|
||||
autoSize
|
||||
:maxlength="150"
|
||||
placeholder="请输入"
|
||||
allowClear
|
||||
v-model:value="ruleForm.remarks"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="suffix">
|
||||
{{ `${ruleForm.remarks.length} / 150` }}
|
||||
</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item class="button" style="text-align: right">
|
||||
<a-button style="margin-right: 12px;border-radius: 4px;" type="default" @click="$emit('cancel')">取消</a-button>
|
||||
<a-button type="primary" style="border-radius: 4px;" @click="onSubmit">确定</a-button>
|
||||
</a-form-item> -->
|
||||
</a-form>
|
||||
<a-modal v-model:visible="visibleTags" title="新建标签" :destroyOnClose="true" :footer="null">
|
||||
<addTag @cancel="visibleTags = false" @update="addTagUpdata"></addTag>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
import { defineComponent, reactive, ref, watch, onBeforeMount, createVNode } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useRouter } from "vue-router";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
} from "@ant-design/icons-vue";
|
||||
import {
|
||||
getTagsList,
|
||||
deleteTags,
|
||||
getSceneListForSelect
|
||||
} from "@/views/ProjectManage/api";
|
||||
import addTag from "@/views/ProjectManage/components/addTag.vue";
|
||||
import useEmitter from "@/composables/useEmitter";
|
||||
export default defineComponent({
|
||||
name: "StandardConceptConfig",
|
||||
components: {
|
||||
addTag,
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
VNodes: (_, { attrs }) => {
|
||||
return attrs.vnodes;
|
||||
}
|
||||
},
|
||||
setup(props, context) {
|
||||
const store = useStore();
|
||||
const emitter = useEmitter();
|
||||
const items = ref([]);
|
||||
const router = useRouter();
|
||||
const formRef = ref();
|
||||
const tagsList = ref([]);
|
||||
const loading = ref(false);
|
||||
const scenesList = ref([]);
|
||||
const isAdmin = ref(false);
|
||||
const isShow = ref(true);
|
||||
const ruleForm = reactive({
|
||||
project_name: "",
|
||||
tags: [],
|
||||
scene_code_info: '22',
|
||||
remarks: '',
|
||||
});
|
||||
const rules = {
|
||||
project_name: [
|
||||
{ required: true, message: "请输入问卷名称", trigger: "blur" },
|
||||
{ min: 1, max: 30, message: "字数超过限制", trigger: "blur" },
|
||||
],
|
||||
scene_code_info: [
|
||||
{ required: true, message: "请选择场景", trigger: "blur" },
|
||||
],
|
||||
};
|
||||
const visibleTags = ref(false);
|
||||
|
||||
// 标签颜色
|
||||
const countColor = (value) => {
|
||||
let style = {};
|
||||
switch (value) {
|
||||
case 1:
|
||||
style = {
|
||||
color: "#4DB8FA",
|
||||
border: "1px solid #4DB8FA",
|
||||
padding: "0 5px",
|
||||
"border-radius": "4px",
|
||||
"line-height": "20px",
|
||||
};
|
||||
break;
|
||||
case 2:
|
||||
style = {
|
||||
color: "#0CC126",
|
||||
// 'border-color' : '#0CC126',
|
||||
border: "1px solid #0CC126",
|
||||
padding: "0 5px",
|
||||
"border-radius": "4px",
|
||||
"line-height": "20px",
|
||||
};
|
||||
break;
|
||||
case 3:
|
||||
style = {
|
||||
color: "#FF8800",
|
||||
// 'border-color' : '#FF8800',
|
||||
border: "1px solid #FF8800",
|
||||
padding: "0 5px",
|
||||
"border-radius": "4px",
|
||||
"line-height": "20px",
|
||||
};
|
||||
break;
|
||||
case 4:
|
||||
style = {
|
||||
color: "#FF374F",
|
||||
// 'border-color' : '#FF374F',
|
||||
border: "1px solid #FF374F",
|
||||
padding: "0 5px",
|
||||
"border-radius": "4px",
|
||||
"line-height": "20px",
|
||||
};
|
||||
break;
|
||||
case 5:
|
||||
style = {
|
||||
color: "#1C6FFF",
|
||||
// 'border-color' : '#1C6FFF',
|
||||
border: "1px solid #1C6FFF",
|
||||
padding: "0 5px",
|
||||
"border-radius": "4px",
|
||||
"line-height": "20px",
|
||||
};
|
||||
break;
|
||||
case 6:
|
||||
style = {
|
||||
color: "#11AEA7",
|
||||
// 'border-color' : '#11AEA7',
|
||||
border: "1px solid #11AEA7",
|
||||
padding: "0 5px",
|
||||
"border-radius": "4px",
|
||||
"line-height": "20px",
|
||||
};
|
||||
break;
|
||||
case 7:
|
||||
style = {
|
||||
color: "#25D8C8",
|
||||
// 'border-color' : '#25D8C8',
|
||||
border: "1px solid #25D8C8",
|
||||
padding: "0 5px",
|
||||
"border-radius": "4px",
|
||||
"line-height": "20px",
|
||||
};
|
||||
break;
|
||||
case 8:
|
||||
style = {
|
||||
color: "#FECB0D",
|
||||
// 'border-color' : '#FECB0D',
|
||||
border: "1px solid #FECB0D",
|
||||
padding: "0 5px",
|
||||
"border-radius": "4px",
|
||||
"line-height": "20px",
|
||||
};
|
||||
// break;
|
||||
// case 9:
|
||||
// style = {
|
||||
// 'color' : '4DB8FA',
|
||||
// 'border-color' : '4DB8FA',
|
||||
// }
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
/** 获取项目标签列表 */
|
||||
const getTagsListRequest = async (val) => {
|
||||
try {
|
||||
const { data } = await getTagsList();
|
||||
tagsList.value = data;
|
||||
} catch (error) {
|
||||
context.message.error(error.data?.message || error.message || "服务器错误");
|
||||
}
|
||||
};
|
||||
// 删除
|
||||
const delRequest = async (id) => {
|
||||
try {
|
||||
const { data } = await deleteTags(id);
|
||||
getTagsListRequest();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
const del = (id) => {
|
||||
Modal.confirm({
|
||||
content: "删除后,此标签会从当前团队的所有问卷中移除,且无法恢复!",
|
||||
icon: () => createVNode(ExclamationCircleOutlined),
|
||||
cancelText: "取消",
|
||||
okText: "确认",
|
||||
zIndex: 100001,
|
||||
onOk: () => {
|
||||
delRequest(id);
|
||||
},
|
||||
});
|
||||
};
|
||||
const edit = (item) => {
|
||||
context.emit("labelEdit", item);
|
||||
};
|
||||
const addItem = () => {
|
||||
emitter.emit("addGroup");
|
||||
};
|
||||
const onAddTag = () => {
|
||||
visibleTags.value = true;
|
||||
};
|
||||
const addTagUpdata = () => {
|
||||
visibleTags.value = false;
|
||||
getTagsListRequest();
|
||||
};
|
||||
// const onSubmitStatus = () => {
|
||||
// formRef.value
|
||||
// .validate()
|
||||
// .then(() => {
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// console.log("error", error);
|
||||
// });
|
||||
// };
|
||||
const handleChange = (value) => {
|
||||
};
|
||||
|
||||
const handleSceneChange = (value) => {
|
||||
const item = scenesList.value.find(item => item.code === +value)
|
||||
ruleForm.scene_code = item.parentCode;
|
||||
};
|
||||
|
||||
const getScenes = async () => {
|
||||
loading.value = true;
|
||||
const data = await getSceneListForSelect();
|
||||
|
||||
if (data?.code) {
|
||||
message.error(data?.message || "获取场景失败,请刷新!");
|
||||
return;
|
||||
}
|
||||
loading.value = false;
|
||||
scenesList.value = normalizeScenes(data?.data || []);
|
||||
}
|
||||
function normalizeScenes(list) {
|
||||
const result = [];
|
||||
const parent = [];
|
||||
let index = 0;
|
||||
list.forEach((item) => {
|
||||
if (item.parentCode > 0) {
|
||||
// if(!item.sn){
|
||||
result.push(item);
|
||||
// }
|
||||
}else {
|
||||
parent.push(item);
|
||||
}
|
||||
})
|
||||
result.forEach(item => {
|
||||
item.parentTitle = parent.find(pItem => pItem.code === item.parentCode).title.slice(0,2);;
|
||||
})
|
||||
result.sort((a, b) => a.sort - b.sort);
|
||||
return result;
|
||||
}
|
||||
function filterOption(inputValue, option) {
|
||||
const reg = new RegExp(inputValue);
|
||||
const result = reg.test(option.label);
|
||||
return result;
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
getTagsListRequest();
|
||||
getScenes();
|
||||
var user = localStorage.getItem("plantUserInfo");
|
||||
user = JSON.parse(user);
|
||||
isAdmin.value = user?.super_admin_flag;
|
||||
});
|
||||
return {
|
||||
formRef,
|
||||
rules,
|
||||
ruleForm,
|
||||
// onSubmitStatus,
|
||||
loading,
|
||||
value: ref([]),
|
||||
handleChange,
|
||||
handleSceneChange,
|
||||
options: [],
|
||||
del,
|
||||
edit,
|
||||
items,
|
||||
addItem,
|
||||
onAddTag,
|
||||
tagsList,
|
||||
filterOption,
|
||||
getTagsListRequest,
|
||||
visibleTags,
|
||||
addTagUpdata,
|
||||
countColor,
|
||||
isShow,
|
||||
scenesList,
|
||||
getScenes,
|
||||
isAdmin
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon {
|
||||
font-size: 16px;
|
||||
.del {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
.tags {
|
||||
margin: 0 5px 5px;
|
||||
min-width: 75px;
|
||||
width: 48px;
|
||||
height: 19px;
|
||||
border-radius: 4px;
|
||||
// position: relative;
|
||||
box-sizing: border-box;
|
||||
.title {
|
||||
// position: absolute;
|
||||
font-size: 12px;
|
||||
font-family: PingFangSC-Regular, PingFang SC;
|
||||
font-weight: 400;
|
||||
line-height: 17px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.tagStyle {
|
||||
border-radius: 4px;
|
||||
max-width: 120px;
|
||||
padding: 0 5px;
|
||||
margin: 0 5px;
|
||||
display: inline-block;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
color: #4db8fa;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
.show-select {
|
||||
.icon {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
::v-deep .ant-input-textarea-clear-icon {
|
||||
margin: 4px 3px 0 0;
|
||||
}
|
||||
.show-select:deep(.ant-select-selector){
|
||||
border-radius:4px;
|
||||
}
|
||||
</style>
|
||||
1056
src/views/Concept/json/concept.json
Normal file
1056
src/views/Concept/json/concept.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -152,6 +152,11 @@
|
||||
:curTemp="curTemp"
|
||||
@labelEdit="labelEdit"
|
||||
/>
|
||||
|
||||
<!-- 概念测试 -->
|
||||
<ConceptTesting
|
||||
ref="conceptTestingRef"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -185,6 +190,8 @@ import {useStore} from "vuex";
|
||||
import {currentMode} from "@/config";
|
||||
import { LeftCircleOutlined, RightCircleOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
import ConceptTesting from "@/views/Concept/ConceptTesting.vue"
|
||||
|
||||
|
||||
const loading = ref(false);
|
||||
const router = useRouter();
|
||||
@@ -393,7 +400,8 @@ function createNormalSurvey(item){
|
||||
}
|
||||
|
||||
function createProfessionalSurvey(record) {
|
||||
// console.log('使用', record);
|
||||
console.log('使用', record);
|
||||
// return
|
||||
curTemp.value = record;
|
||||
temp_sn.value = record.sn;
|
||||
groupInfo.value.scene_code_info = `${record.code}`;
|
||||
@@ -411,6 +419,9 @@ function createProfessionalSurvey(record) {
|
||||
curTemp.value.type = 1;
|
||||
return createSurveySellRef.value.openModal();
|
||||
}
|
||||
else if ([36, 37, 38].includes(record.code)) {
|
||||
return conceptTestingRef.value.openModal();
|
||||
}
|
||||
else{
|
||||
Modal.confirm({
|
||||
title: () => '创建问卷',
|
||||
@@ -495,6 +506,8 @@ function professionalPrev(){
|
||||
})
|
||||
}
|
||||
|
||||
/** 概念测试 */
|
||||
const conceptTestingRef = ref(null)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -178,6 +178,11 @@
|
||||
:curTemp="curTemp"
|
||||
:zIndex="10000"
|
||||
/>
|
||||
|
||||
<!-- 概念测试 -->
|
||||
<ConceptTesting
|
||||
ref="conceptTestingRef"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@@ -191,6 +196,8 @@ import Search from "../components/TempSearch.vue"
|
||||
import { getGroupList } from "@/api/template-market";
|
||||
import CreateSurvey from "@views/ProjectManage/components/NewCreateSurvey.vue";
|
||||
|
||||
import ConceptTesting from "@/views/Concept/ConceptTesting.vue"
|
||||
|
||||
import {
|
||||
nextTick,
|
||||
defineComponent,
|
||||
@@ -275,6 +282,7 @@ export default defineComponent({
|
||||
CreateSurveySell,
|
||||
CreateSurveyProduct,
|
||||
Search,
|
||||
ConceptTesting,
|
||||
},
|
||||
props: {
|
||||
groupId: { type: Number, value: 0 },
|
||||
@@ -304,6 +312,7 @@ export default defineComponent({
|
||||
const createSurveyRef = ref();
|
||||
const createSurveySellRef = ref();
|
||||
const createSurveyProductRef = ref();
|
||||
const conceptTestingRef = ref(null); /** 概念测试 */
|
||||
const editLabelVisible = ref(false);
|
||||
const editLabelItem = ref({});
|
||||
const preview_visible = ref(false);
|
||||
@@ -555,6 +564,12 @@ export default defineComponent({
|
||||
createSurveyProductRef.value.openModal();
|
||||
} else if (+record.type === 1) {
|
||||
createSurveySellRef.value.openModal();
|
||||
} else if (record.type === 300) {
|
||||
conceptTestingRef.value.openModal(1);
|
||||
} else if (record.type === 301) {
|
||||
conceptTestingRef.value.openModal(2);
|
||||
} else if (record.type === 302) {
|
||||
conceptTestingRef.value.openModal(3);
|
||||
} else {
|
||||
groupInfo.value.sn = "";
|
||||
groupInfo.value.group_id = 0;
|
||||
@@ -686,6 +701,7 @@ export default defineComponent({
|
||||
createSurveyRef,
|
||||
createSurveySellRef,
|
||||
createSurveyProductRef,
|
||||
conceptTestingRef,
|
||||
handlePreview,
|
||||
handleMove,
|
||||
handleRemove,
|
||||
|
||||
Reference in New Issue
Block a user