detail.vue 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173
  1. <!-- アンケート照会 -->
  2. <template>
  3. <div class="app-container">
  4. <p>アンケートフォーム作成</p>
  5. <div class="form-container">
  6. <el-form ref="queryRef"
  7. :model="queryParams"
  8. label-width="140px"
  9. label-position="top">
  10. <el-form-item label="タイトル" style="margin-left: 30px;margin-top: -35px;">
  11. <el-input v-model="queryParams.title" class="table-input" :maxlength="100" disabled></el-input>
  12. </el-form-item>
  13. <el-form-item label="説明文" style="margin-left: 30px;margin-top: -15px;" >
  14. <el-input
  15. v-model="queryParams.description"
  16. class="table-input"
  17. :maxlength="255"
  18. type="textarea"
  19. :rows="2"
  20. disabled
  21. ></el-input>
  22. </el-form-item>
  23. <div style=" padding-left: 29px;margin-top: -15px;">公開範囲</div>
  24. <el-form-item label="FC" prop="brandCode" style="margin-left: 60px; display: flex; align-items: center; margin-top: 10px;">
  25. <div class="el-form-item__label" style="flex-shrink: 0; margin-right: 20px;"></div>
  26. <el-checkbox-group v-model="queryParams.brandCode">
  27. <el-checkbox disabled v-for="dict in yamada_fc_brand" :key="dict.value" :label="dict.value" style="margin-right: 60px;">{{ dict.label }}</el-checkbox>
  28. </el-checkbox-group>
  29. </el-form-item>
  30. <el-form-item label="業種" prop="businessTypeCode" class="el-form-item__businessTypeCode">
  31. <div class="el-form-item__label" style="flex-shrink: 0; margin-right: 14px;"></div>
  32. <el-checkbox-group v-model="queryParams.businessTypeCode">
  33. <el-checkbox disabled v-for="dict in yamada_business_type" :key="dict.value" :label="dict.value" style="margin-right: 32px;">{{ dict.label }}</el-checkbox>
  34. </el-checkbox-group>
  35. </el-form-item>
  36. <div class="container">
  37. <div class="area-header-row">
  38. <h1 class="area-title">エリア</h1>
  39. <RegionTree class="region-tree-container" :treeData="regionTree" @check-change="handleCheckChange" disabled />
  40. </div>
  41. </div>
  42. <!-- 公開期間 -->
  43. <div class="form-section">公開期間</div>
  44. <!-- 垂直排列的单选框组 -->
  45. <el-form-item class="radio-group-vertical">
  46. <div class="radio-container"> <!-- 新增容器用于控制布局 -->
  47. <el-radio
  48. label="immediate"
  49. v-model="queryParams.releaseType"
  50. class="vertical-radio"
  51. disabled
  52. >即時公開</el-radio>
  53. <el-radio
  54. label="scheduled"
  55. v-model="queryParams.releaseType"
  56. class="scheduled-radio"
  57. disabled
  58. >期間指定</el-radio>
  59. <div class="scheduled-row">
  60. <!-- 期間指定时显示的日期选择(同行) -->
  61. <div class="date-group" >
  62. <!-- 日期选择器代码保持不变 -->
  63. <el-select v-model="queryParams.startYear" placeholder="" class="date-select" disabled @change="formatDate">
  64. <el-option v-for="year in years" :key="year" :label="year" :value="year"></el-option>
  65. </el-select>
  66. <span class="date-label">年</span>
  67. <el-select
  68. v-model="queryParams.startMonth"
  69. placeholder=""
  70. class="date-select-small"
  71. @change="formatDate"
  72. disabled
  73. >
  74. <el-option
  75. v-for="month in months"
  76. :key="month"
  77. :label="month"
  78. :value="month"
  79. ></el-option>
  80. </el-select>
  81. <span class="date-label">月</span>
  82. <el-select
  83. v-model="queryParams.startDay"
  84. placeholder=""
  85. class="date-select-small"
  86. @change="formatDate"
  87. disabled
  88. >
  89. <el-option
  90. v-for="day in days"
  91. :key="day"
  92. :label="day"
  93. :value="day"
  94. ></el-option>
  95. </el-select>
  96. <span class="date-label">日</span>
  97. <span class="date-label">~</span>
  98. <el-select
  99. v-model="queryParams.endYear"
  100. placeholder=""
  101. class="date-select"
  102. @change="formatDate"
  103. disabled
  104. >
  105. <el-option
  106. v-for="year in years"
  107. :key="year"
  108. :label="year"
  109. :value="year"
  110. ></el-option>
  111. </el-select>
  112. <span class="date-label">年</span>
  113. <el-select
  114. v-model="queryParams.endMonth"
  115. placeholder=""
  116. class="date-select-small"
  117. @change="formatDate"
  118. disabled
  119. >
  120. <el-option
  121. v-for="month in months"
  122. :key="month"
  123. :label="month"
  124. :value="month"
  125. ></el-option>
  126. </el-select>
  127. <span class="date-label">月</span>
  128. <el-select
  129. v-model="queryParams.endDay"
  130. placeholder=""
  131. class="date-select-small"
  132. @change="formatDate"
  133. disabled
  134. >
  135. <el-option
  136. v-for="day in days"
  137. :key="day"
  138. :label="day"
  139. :value="day"
  140. ></el-option>
  141. </el-select>
  142. <span class="date-label">日</span>
  143. </div>
  144. </div>
  145. </div>
  146. </el-form-item>
  147. <div style="margin-left: 25px; margin-top: 15px;">自由作成フォーム</div>
  148. <div v-for="(question, index) in questions" :key="question.sortOrder" class="question-block">
  149. <el-form-item style="margin-left: 50px; margin-top: 3px;">
  150. <el-button disabled class="append-button" @click="addQuestionAbove(index)">この上に設問を追加</el-button>
  151. </el-form-item>
  152. <el-form-item style="margin-left: 55px;">
  153. <template #label>
  154. <div style="display: flex; align-items: center;margin-top: -20px">
  155. <span style="color: #606266; font-weight: bold">設問文</span>
  156. <span style="color: #666; margin-left: 8px;">: {{ question.sortOrder }}</span>
  157. </div>
  158. </template>
  159. <el-input disabled v-model="question.questionText" class="small-input" type="textarea" :rows="2" :maxlength="255"></el-input>
  160. </el-form-item>
  161. <el-form-item label="回答型" style="margin-left: 55px;margin-top: -15px">
  162. <el-radio-group disabled v-model="question.questionTypeStr" class="custom-radio-group" @change="handleQuestionTypeChange(question, $event)">
  163. <el-radio label="INPUT_BOX_50">入力ボックス(50文字)</el-radio>
  164. <el-radio label="INPUT_BOX_100">入力ボックス(100文字)</el-radio>
  165. <el-radio label="CHECK_BOX">チェックボックス</el-radio>
  166. <el-radio label="RADIO_BUTTON">ラジオボタン</el-radio>
  167. <el-radio label="NUMBER_SELECT">数字選択(プルダウン)</el-radio>
  168. </el-radio-group>
  169. </el-form-item>
  170. <el-form-item>
  171. <el-input
  172. v-if="question.questionTypeStr === 'INPUT_BOX_50'"
  173. v-model="question.input50"
  174. :maxlength="50"
  175. class="textarea-input"
  176. type="textarea"
  177. :rows="2"
  178. disabled
  179. ></el-input>
  180. </el-form-item>
  181. <el-form-item>
  182. <el-input
  183. v-if="question.questionTypeStr === 'INPUT_BOX_100'"
  184. v-model="question.input100"
  185. :maxlength="100"
  186. class="textarea-input100"
  187. type="textarea"
  188. :rows="2"
  189. disabled
  190. ></el-input>
  191. </el-form-item>
  192. <el-form-item v-if="question.questionTypeStr === 'CHECK_BOX'" style="margin-left: 125px;margin-top: -60px;">
  193. <!-- 循环渲染每一行:复选框 + 输入框 + 削除按钮 -->
  194. <div
  195. v-for="(item, itemIndex) in question.checkboxItems"
  196. :key="itemIndex"
  197. class="checkbox-row"
  198. >
  199. <div style="display: flex; align-items: center; width: 100%;">
  200. <span style="min-width: 30px; color: #666;">{{ item.sortOrder || itemIndex + 1 }}</span>
  201. <el-checkbox
  202. v-model="item.checked"
  203. class="checkbox-item"
  204. disabled
  205. ></el-checkbox>
  206. <el-input
  207. v-model="item.optionText"
  208. class="input-item"
  209. disabled
  210. ></el-input>
  211. <el-button
  212. type="text"
  213. class="delete-btn"
  214. @click="deleteCheckboxItem(index, itemIndex)"
  215. disabled
  216. >削除</el-button>
  217. </div>
  218. </div>
  219. <el-button
  220. class="append-btn"
  221. @click="addCheckboxItem(index)"
  222. disabled
  223. >追加</el-button>
  224. </el-form-item>
  225. <el-form-item v-if="question.questionTypeStr === 'RADIO_BUTTON'" style="margin-left: 125px;margin-top: -60px;">
  226. <!-- 循环渲染每一行:单选按钮 + 输入框 + 削除按钮 -->
  227. <div
  228. v-for="(item, itemIndex) in question.radioItems"
  229. :key="itemIndex"
  230. class="checkbox-row"
  231. >
  232. <div style="display: flex; align-items: center; width: 100%;">
  233. <span style="min-width: 30px; color: #666;">{{ item.sortOrder || itemIndex + 1 }}</span>
  234. <el-radio
  235. :label="item.value"
  236. v-model="question.radioGroupValue"
  237. class="hidden-label-radio"
  238. disabled
  239. ></el-radio>
  240. <el-input
  241. v-model="item.optionText"
  242. class="radio-item"
  243. disabled
  244. ></el-input>
  245. <el-button
  246. type="text"
  247. class="radio-btn"
  248. @click="deleteRadioItem(index, itemIndex)"
  249. disabled
  250. >削除</el-button>
  251. </div>
  252. </div>
  253. <el-button
  254. class="append-btn"
  255. @click="addRadioItem(index)"
  256. disabled
  257. >追加</el-button>
  258. </el-form-item>
  259. <el-form-item v-if="question.questionTypeStr === 'NUMBER_SELECT'" style="margin-left: 120px;margin-top: -50px">
  260. <div style="display: flex; flex-direction: column; gap: 10px; width: 100%;">
  261. <!-- 選択肢部分 -->
  262. <div style="display: flex; align-items: center; width: 100%;">
  263. <span>選択肢</span>
  264. <div style="width: 35px;"></div>
  265. <el-input type="number" disabled min="0" max="999" v-model="question.numberSelectStart" class="mx-2"
  266. style="width: 60px;margin-right: 10px;"></el-input>
  267. <span>~</span>
  268. <el-input disabled type="number" min="0" max="999" v-model="question.numberSelectEnd" class="mx-2" style="width: 60px;margin-left: 10px;"></el-input>
  269. </div>
  270. <!-- 単位部分 -->
  271. <div style="display: flex; align-items: center; width: 100%; flex-wrap: nowrap; white-space: nowrap;">
  272. <span>単位</span>
  273. <el-radio-group disabled v-model="question.unitHas" class="mx-radio-group">
  274. <el-radio :label="false" class="radio-group">なし</el-radio>
  275. <el-radio :label="true">あり</el-radio>
  276. </el-radio-group>
  277. <el-input
  278. v-model="question.questionUnit"
  279. :maxlength="20"
  280. style="width: 85px; margin-left: 10px;"
  281. :disabled="!question.unitHas"
  282. ></el-input>
  283. </div>
  284. </div>
  285. </el-form-item>
  286. <el-form-item style="margin-left: 50px; margin-top: -10px;">
  287. <el-button class="append-button" disabled @click="addQuestionBelow(index)">この下に設問を追加</el-button>
  288. </el-form-item>
  289. </div>
  290. </el-form>
  291. </div>
  292. </div>
  293. </template>
  294. <script name="SurveyDetail" setup>
  295. import { getRegionTree,listSurveyDetail} from "@/api/fcbi/survey.js"
  296. import { reactive, toRefs, ref, watch, onMounted } from 'vue';
  297. import RegionTree from '../../../components/RegionTree.vue';
  298. import { ElMessage } from "element-plus";
  299. import {useRoute, useRouter} from 'vue-router';
  300. import useSurveyStore from '@/store/modules/survey';
  301. const { proxy } = getCurrentInstance();
  302. const surveyStore = useSurveyStore()
  303. // アップロード対象を取得
  304. const { yamada_fc_brand } = proxy.useDict('yamada_fc_brand');
  305. surveyStore.setBrandCode(yamada_fc_brand)
  306. const { yamada_business_type } = proxy.useDict('yamada_business_type');
  307. surveyStore.setBusinessTypeCode(yamada_business_type)
  308. const error = ref('');
  309. const regionTree = ref([]);
  310. const selectedRegions = ref([]);
  311. const router = useRouter();
  312. const route= useRoute()
  313. const surveyId = route.params.surveyId
  314. /**
  315. * コンポーネントの状態を管理するリアクティブオブジェクト
  316. */
  317. const data = reactive({
  318. form: {},
  319. // 検索条件を格納するオブジェクト
  320. queryParams: {
  321. title: null,
  322. description: null,
  323. brandCode: [],
  324. businessTypeCode: [],
  325. releaseType: 'immediate',
  326. startYear: '',
  327. startMonth: '',
  328. startDay: '',
  329. endYear: '',
  330. endMonth: '',
  331. endDay: '',
  332. publicStartTime: '',
  333. publicEndTime: '',
  334. publicMode: '',
  335. insertFlag: 1,
  336. regions: []
  337. },
  338. questions: [
  339. {
  340. questionText: '',
  341. questionTypeStr: 'INPUT_BOX_50',
  342. oldQuestionTypeStr: 'INPUT_BOX_50',
  343. input50: '',
  344. input100: '',
  345. checkboxItems: [
  346. { checked: false, optionText: '', sortOrder: 1 },
  347. { checked: false, optionText: '', sortOrder: 2 },
  348. { checked: false, optionText: '', sortOrder: 3 },
  349. ],
  350. radioItems: [
  351. { value: 'option1', optionText: '', sortOrder: 1 },
  352. { value: 'option2', optionText: '', sortOrder: 2 },
  353. { value: 'option3', optionText: '', sortOrder: 3 },
  354. ],
  355. radioGroupValue: null,
  356. numberSelectStart: '',
  357. numberSelectEnd: '',
  358. unitHas: false,
  359. sortOrder: 1,
  360. questionUnit: ''
  361. }
  362. ]
  363. });
  364. // 問題を動的に追加する方法
  365. const addQuestionAbove = (index) => {
  366. const newSortOrder = data.questions[index].sortOrder;
  367. // 指定した場所に新しい問題を追加する
  368. data.questions.splice(index, 0, {
  369. questionText: '',
  370. questionType: 'INPUT_BOX_50',
  371. oldQuestionType: 'INPUT_BOX_50',
  372. input50: '',
  373. input100: '',
  374. checkboxItems: [
  375. { checked: false, optionText: '', sortOrder: 1 },
  376. { checked: false, optionText: '', sortOrder: 2 },
  377. { checked: false, optionText: '', sortOrder: 3 },
  378. ],
  379. radioItems: [
  380. { value: 'option1', optionText: '', sortOrder: 1 },
  381. { value: 'option2', optionText: '', sortOrder: 2 },
  382. { value: 'option3', optionText: '', sortOrder: 3 },
  383. ],
  384. radioGroupValue: null,
  385. numberSelectStart: '',
  386. numberSelectEnd: '',
  387. unitHas: false,
  388. sortOrder: newSortOrder,
  389. questionUnit: ''
  390. });
  391. adjustSortOrders(index);
  392. };
  393. const addQuestionBelow = (index) => {
  394. const newSortOrder = data.questions[index].sortOrder + 1;
  395. // 指定した場所の下に新しい問題を追加する
  396. data.questions.splice(index + 1, 0, {
  397. questionText: '',
  398. questionType: 'INPUT_BOX_50',
  399. oldQuestionType: 'INPUT_BOX_50',
  400. input50: '',
  401. input100: '',
  402. checkboxItems: [
  403. { checked: false, optionText: '', sortOrder: 1 },
  404. { checked: false, optionText: '', sortOrder: 2 },
  405. { checked: false, optionText: '', sortOrder: 3 },
  406. ],
  407. radioItems: [
  408. { value: 'option1', optionText: '', sortOrder: 1 },
  409. { value: 'option2', optionText: '', sortOrder: 2 },
  410. { value: 'option3', optionText: '', sortOrder: 3 },
  411. ],
  412. radioGroupValue: null,
  413. numberSelectStart: '',
  414. numberSelectEnd: '',
  415. unitHas: false,
  416. sortOrder: newSortOrder,
  417. questionUnit: ''
  418. });
  419. adjustSortOrders(index + 1);
  420. };
  421. // 問題のソートの調整
  422. const adjustSortOrders = (startIndex) => {
  423. for (let i = startIndex; i < data.questions.length; i++) {
  424. data.questions[i].sortOrder = i + 1;
  425. }
  426. };
  427. // 回答类型切换时重置前一类型的状态
  428. const handleQuestionTypeChange = (question, newType) => {
  429. // 保存旧类型用于判断需要重置哪些数据
  430. const oldType = question.questionTypeStr;
  431. // 如果类型未变化,不执行重置
  432. if (oldType === newType) return;
  433. // 根据旧类型重置对应的数据(回到初始状态)
  434. switch (oldType) {
  435. case 'INPUT_BOX_50':
  436. question.input50 = ''; // 重置50文字输入框
  437. break;
  438. case 'INPUT_BOX_100':
  439. question.input100 = ''; // 重置100文字输入框
  440. break;
  441. case 'CHECK_BOX':
  442. // 重置复选框选项(保留3个空选项)
  443. question.checkboxItems = [
  444. { checked: false, optionText: '', sortOrder: 1 },
  445. { checked: false, optionText: '', sortOrder: 2 },
  446. { checked: false, optionText: '', sortOrder: 3 },
  447. ];
  448. break;
  449. case 'RADIO_BUTTON':
  450. // 重置单选按钮选项(保留3个空选项)
  451. question.radioItems = [
  452. { value: 'option1', optionText: '', sortOrder: 1 },
  453. { value: 'option2', optionText: '', sortOrder: 2 },
  454. { value: 'option3', optionText: '', sortOrder: 3 },
  455. ];
  456. question.radioGroupValue = null; // 重置选中状态
  457. break;
  458. case 'NUMBER_SELECT':
  459. // 重置数字选择器相关数据
  460. question.numberSelectStart = '';
  461. question.numberSelectEnd = '';
  462. question.unitHas = false; // 重置单位选择
  463. question.questionUnit = ''; // 重置单位输入框
  464. break;
  465. }
  466. // 更新为新类型(确保数据同步)
  467. question.questionTypeStr = newType;
  468. };
  469. //ボタンの追加方法
  470. const addCheckboxItem = (questionIndex) => {
  471. const question = data.questions[questionIndex];
  472. const newSortOrder = question.checkboxItems.length + 1;
  473. question.checkboxItems.push({
  474. checked: false,
  475. optionText: '',
  476. sortOrder: newSortOrder
  477. });
  478. };
  479. const addRadioItem = (questionIndex) => {
  480. const question = data.questions[questionIndex];
  481. const newId = question.radioItems.length + 1;
  482. const newSortOrder = question.radioItems.length + 1;
  483. question.radioItems.push({
  484. value: `option${newId}`,
  485. optionText: '',
  486. sortOrder: newSortOrder
  487. });
  488. };
  489. //消去ボタンの方法
  490. const deleteRadioItem = (questionIndex, itemIndex) => {
  491. const question = data.questions[questionIndex];
  492. if (question.radioItems.length <= 3) {
  493. ElMessage.warning('少なくとも3つの選択肢を残してください');
  494. return;
  495. }
  496. question.radioItems.splice(itemIndex, 1);
  497. question.radioItems.forEach((item, index) => {
  498. item.sortOrder = index + 1;
  499. });
  500. };
  501. const deleteCheckboxItem = (questionIndex, itemIndex) => {
  502. const question = data.questions[questionIndex];
  503. if (question.checkboxItems.length <= 3) {
  504. ElMessage.warning('少なくとも3つの選択肢を残してください');
  505. return;
  506. }
  507. question.checkboxItems.splice(itemIndex, 1);
  508. question.checkboxItems.forEach((item, index) => {
  509. item.sortOrder = index + 1;
  510. });
  511. };
  512. // リアクティブオブジェクトからプロパティを参照可能なオブジェクトに変換
  513. const { queryParams, questions } = toRefs(data);
  514. const currentYear = new Date().getFullYear();
  515. const years = Array.from({ length: 11 }, (_, i) => currentYear + i);
  516. const months = Array.from({ length: 12 }, (_, i) => i + 1);
  517. const days = Array.from({ length: 31 }, (_, i) => i + 1);
  518. const formatDate = () => {
  519. if (queryParams.value.releaseType === 'scheduled') {
  520. const publicStartTime = `${queryParams.value.startYear}-${String(queryParams.value.startMonth).padStart(2, '0')}-${String(queryParams.value.startDay).padStart(2, '0')}`;
  521. const publicEndTime = `${queryParams.value.endYear}-${String(queryParams.value.endMonth).padStart(2, '0')}-${String(queryParams.value.endDay).padStart(2, '0')}`;
  522. queryParams.publicStartTime = publicStartTime;
  523. queryParams.publicEndTime = publicEndTime;
  524. queryParams.publicMode = 'PERIOD';
  525. } else {
  526. queryParams.publicStartTime = '';
  527. queryParams.publicEndTime = '';
  528. queryParams.value.startYear = '';
  529. queryParams.value.startMonth = '';
  530. queryParams.value.startDay = '';
  531. queryParams.value.endYear = '';
  532. queryParams.value.endMonth = '';
  533. queryParams.value.endDay = '';
  534. queryParams.publicMode = 'INSTANT';
  535. }
  536. };
  537. watch(() => queryParams.value.releaseType, () => {
  538. formatDate();
  539. });
  540. watch(regionTree, () => {
  541. if (regionTree.value.length > 0) {
  542. updateSelectedRegions(regionTree.value);
  543. }
  544. }, { deep: true });
  545. watch(
  546. () => data.questions.map(q => q.unitHas),
  547. (newValues, oldValues) => {
  548. data.questions.forEach((question, index) => {
  549. if (newValues[index] === false && oldValues[index] === true) {
  550. question.questionUnit = '';
  551. }
  552. });
  553. },
  554. { deep: true }
  555. );
  556. watch(
  557. () => data.questions.map(q => q.questionTypeStr),
  558. (newTypes, oldTypes) => {
  559. data.questions.forEach((question, index) => {
  560. const newType = newTypes[index];
  561. const oldType = oldTypes[index];
  562. if (newType === oldType) return;
  563. resetOldTypeData(question, oldType);
  564. question.oldQuestionTypeStr = oldType; // 记录旧类型
  565. });
  566. },
  567. { deep: true }
  568. );
  569. // 重置旧类型对应的所有数据
  570. const resetOldTypeData = (question, oldType) => {
  571. switch (oldType) {
  572. case 'INPUT_BOX_50':
  573. question.input50 = '';
  574. break;
  575. case 'INPUT_BOX_100':
  576. question.input100 = '';
  577. break;
  578. case 'CHECK_BOX':
  579. question.checkboxItems = [
  580. { checked: false, optionText: '', sortOrder: 1 },
  581. { checked: false, optionText: '', sortOrder: 2 },
  582. { checked: false, optionText: '', sortOrder: 3 },
  583. ];
  584. break;
  585. case 'RADIO_BUTTON':
  586. question.radioItems = [
  587. { value: 'option1', optionText: '', sortOrder: 1 },
  588. { value: 'option2', optionText: '', sortOrder: 2 },
  589. { value: 'option3', optionText: '', sortOrder: 3 },
  590. ];
  591. question.radioGroupValue = null;
  592. break;
  593. case 'NUMBER_SELECT':
  594. question.numberSelectStart = '';
  595. question.numberSelectEnd = '';
  596. question.unitHas = false;
  597. question.questionUnit = '';
  598. break;
  599. }
  600. };
  601. onMounted(async () => {
  602. // 先加载地区数据
  603. const regionResponse = await getRegionTree();
  604. if (regionResponse?.success) {
  605. const regions = regionResponse.data.regions || []
  606. regionTree.value = regions
  607. surveyStore.setRegionTree(regions)
  608. if (regionTree.value.length > 0) {
  609. updateSelectedRegions(regionTree.value);
  610. }
  611. } else {
  612. error.value = regionResponse?.message || '地域データの取得に失敗しました';
  613. console.error('地域データの取得に失敗しました:', regionResponse);
  614. }
  615. // 加载问卷详情数据
  616. if (surveyId) {
  617. try {
  618. const requestData = { surveyId };
  619. const surveyResponse = await listSurveyDetail(requestData);
  620. if (surveyResponse.code === 200) {
  621. // 从返回结果中获取问卷数据(假设rows是数组,取第一个元素)
  622. const surveyData = surveyResponse.rows?.[0] || {};
  623. // 绑定基本信息(标题、描述、公开范围等)
  624. bindSurveyBasicInfo(surveyData);
  625. // 处理问题数据(后端返回的是JSON字符串)
  626. let questionArray = [];
  627. if (surveyData.question) {
  628. try {
  629. // 解析JSON字符串为数组
  630. questionArray = JSON.parse(surveyData.question);
  631. // 验证解析结果是否为数组
  632. if (!Array.isArray(questionArray)) {
  633. console.warn('question字段解析后不是数组,已初始化为空数组');
  634. questionArray = [];
  635. }
  636. } catch (e) {
  637. // 处理JSON解析错误
  638. console.error('question字段JSON解析失败:', e);
  639. console.error('原始字符串内容:', surveyData.question);
  640. ElMessage.error('アンケートの問題データ形式が無効です');
  641. questionArray = [];
  642. }
  643. } else {
  644. // 处理question字段为空的情况
  645. console.log('question字段为空,初始化为空数组');
  646. questionArray = [];
  647. }
  648. // 绑定问题列表
  649. bindSurveyQuestions(questionArray);
  650. // 绑定地区选择
  651. bindSelectedRegions(surveyData.region || []);
  652. } else {
  653. // 处理接口返回错误状态
  654. ElMessage.error(surveyResponse?.message || 'アンケートデータの取得に失敗しました');
  655. }
  656. } catch (err) {
  657. // 处理接口调用异常
  658. console.error('アンケートデータの取得中にエラーが発生しました:', err);
  659. ElMessage.error('アンケートデータの取得中にエラーが発生しました');
  660. }
  661. }
  662. });
  663. const bindSurveyBasicInfo = (surveyData) => {
  664. // 标题和描述
  665. queryParams.value.title = surveyData.title || '';
  666. queryParams.value.description = surveyData.description || '';
  667. // 公开范围 - FC
  668. if (surveyData.brandCode) {
  669. queryParams.value.brandCode = typeof surveyData.brandCode === 'string'
  670. ? JSON.parse(surveyData.brandCode)
  671. : (surveyData.brandCode || []);
  672. } else {
  673. queryParams.value.brandCode = [];
  674. }
  675. // 公开范围 - 業種
  676. if (surveyData.businessTypeCode) {
  677. queryParams.value.businessTypeCode = typeof surveyData.businessTypeCode === 'string'
  678. ? JSON.parse(surveyData.businessTypeCode)
  679. : (surveyData.businessTypeCode || []);
  680. } else {
  681. queryParams.value.businessTypeCode = [];
  682. }
  683. // 公开期间
  684. if (surveyData.publicMode === 'INSTANT') {
  685. queryParams.value.releaseType = 'immediate';
  686. } else if (surveyData.publicMode === 'PERIOD') {
  687. queryParams.value.releaseType = 'scheduled';
  688. // 解析开始日期
  689. if (surveyData.publicStartTime) {
  690. const startDate = new Date(surveyData.publicStartTime);
  691. queryParams.value.startYear = startDate.getFullYear();
  692. queryParams.value.startMonth = startDate.getMonth() + 1;
  693. queryParams.value.startDay = startDate.getDate();
  694. }
  695. // 解析结束日期
  696. if (surveyData.publicEndTime) {
  697. const endDate = new Date(surveyData.publicEndTime);
  698. queryParams.value.endYear = endDate.getFullYear();
  699. queryParams.value.endMonth = endDate.getMonth() + 1;
  700. queryParams.value.endDay = endDate.getDate();
  701. }
  702. formatDate();
  703. }
  704. };
  705. const bindSurveyQuestions = (questionsData) => {
  706. if (!questionsData.length) return;
  707. // 清空现有问题
  708. data.questions = [];
  709. // 绑定每个问题
  710. questionsData.forEach((q, index) => {
  711. const question = {
  712. questionText: q.questionText || '',
  713. questionTypeStr: q.questionTypeStr || 'INPUT_BOX_50',
  714. oldQuestionTypeStr: q.questionTypeStr || 'INPUT_BOX_50',
  715. input50: '',
  716. input100: '',
  717. checkboxItems: [],
  718. radioItems: [],
  719. radioGroupValue: null,
  720. numberSelectStart: '',
  721. numberSelectEnd: '',
  722. unitHas: false,
  723. sortOrder: index + 1,
  724. questionUnit: ''
  725. };
  726. // 根据问题类型绑定不同的内容
  727. switch (question.questionTypeStr) {
  728. case 'INPUT_BOX_50':
  729. question.input50 = q.input50 || '';
  730. break;
  731. case 'INPUT_BOX_100':
  732. question.input100 = q.input100 || '';
  733. break;
  734. case 'CHECK_BOX':
  735. question.checkboxItems = q.checkboxItems || [
  736. { checked: false, optionText: '', sortOrder: 1 },
  737. { checked: false, optionText: '', sortOrder: 2 },
  738. { checked: false, optionText: '', sortOrder: 3 },
  739. ];
  740. break;
  741. case 'RADIO_BUTTON':
  742. question.radioItems = q.radioItems || [
  743. { value: 'option1', optionText: '', sortOrder: 1 },
  744. { value: 'option2', optionText: '', sortOrder: 2 },
  745. { value: 'option3', optionText: '', sortOrder: 3 },
  746. ];
  747. question.radioGroupValue = q.radioGroupValue || null;
  748. break;
  749. case 'NUMBER_SELECT':
  750. question.numberSelectStart = q.numberSelectStart || '';
  751. question.numberSelectEnd = q.numberSelectEnd || '';
  752. question.unitHas = q.unitHas || false;
  753. question.questionUnit = q.questionUnit || '';
  754. break;
  755. }
  756. data.questions.push(question);
  757. });
  758. };
  759. const bindSelectedRegions = (region) => {
  760. const regionData = typeof region === 'string' ? JSON.parse(region) : region;
  761. selectedRegions.value = [...regionData];
  762. queryParams.value.regions = [...regionData];
  763. regionData.forEach(region => {
  764. checkRegionNode(regionTree.value, region.regionCode);
  765. });
  766. };
  767. const checkRegionNode = (nodes, regionCode, parentNode = null) => {
  768. for (const node of nodes) {
  769. if (node.regionCode === regionCode) {
  770. node.checked = true;
  771. // 子节点选中后,更新其父节点状态
  772. updateParentNodeStatus(parentNode);
  773. return true;
  774. }
  775. if (node.children && node.children.length > 0) {
  776. // 递归查找子节点时,将当前节点作为父节点传入
  777. const found = checkRegionNode(node.children, regionCode, node);
  778. if (found) return true;
  779. }
  780. }
  781. return false;
  782. };
  783. const updateParentNodeStatus = (parentNode) => {
  784. if (!parentNode) return; // 没有父节点则终止
  785. const children = parentNode.children || [];
  786. // 统计子节点中选中的数量
  787. const checkedCount = children.filter(child => child.checked).length;
  788. if (checkedCount === 0) {
  789. // 没有子节点选中:父节点不选中,也不是半选
  790. parentNode.checked = false;
  791. parentNode.indeterminate = false;
  792. } else if (checkedCount === children.length) {
  793. // 所有子节点都选中:父节点选中,不是半选
  794. parentNode.checked = true;
  795. parentNode.indeterminate = false;
  796. } else {
  797. // 部分子节点选中:父节点不选中,但为半选
  798. parentNode.checked = false;
  799. parentNode.indeterminate = true;
  800. }
  801. // 继续向上更新父节点的父节点(递归)
  802. updateParentNodeStatus(parentNode.parent);
  803. };
  804. // 選択した状態変更の処理
  805. const handleCheckChange = (regionId, isChecked) => {
  806. const region = findRegionById(regionTree.value, regionId);
  807. if (region) {
  808. if (isChecked) {
  809. if (!selectedRegions.value.some(r => r.regionCode === region.regionCode)) {
  810. selectedRegions.value.push({
  811. regionCode: region.regionCode // 字段名修改
  812. });
  813. }
  814. } else {
  815. selectedRegions.value = selectedRegions.value.filter(r => r.regionCode !== region.regionCode);
  816. }
  817. queryParams.value.regions = selectedRegions.value;
  818. }
  819. };
  820. const findRegionById = (nodes, id) => {
  821. for (const node of nodes) {
  822. if (node.id === id) {
  823. return node;
  824. }
  825. if (node.children && node.children.length > 0) {
  826. const found = findRegionById(node.children, id);
  827. if (found) {
  828. return found;
  829. }
  830. }
  831. }
  832. return null;
  833. };
  834. // 選択したすべての領域を再帰的に更新し、サブノードのみを収集
  835. const updateSelectedRegions = (nodes) => {
  836. selectedRegions.value = [];
  837. // 最上位ノードは親ノードとしてマークされ、再帰的になる
  838. collectSelectedRegions(nodes, true);
  839. queryParams.value.regions = selectedRegions.value;
  840. };
  841. // 選択したすべてのサブエリアオブジェクトを再帰的に収集する(親ノードをスキップ)
  842. const collectSelectedRegions = (nodes, isParent = false) => {
  843. nodes.forEach(node => {
  844. if (isParent) {
  845. if (node.children && node.children.length > 0) {
  846. collectSelectedRegions(node.children, false);
  847. }
  848. return;
  849. }
  850. if (node.checked) {
  851. selectedRegions.value.push({
  852. regionCode: node.regionCode
  853. });
  854. }
  855. if (node.children && node.children.length > 0) {
  856. collectSelectedRegions(node.children, false);
  857. }
  858. });
  859. };
  860. </script>
  861. <style>
  862. /* 小さい入力框样式(設問文输入框) */
  863. .small-input {
  864. width: 600px;
  865. margin-left: 20px;
  866. margin-top: -10px;
  867. }
  868. /* 表格输入框样式(タイトル和説明文输入框) */
  869. .table-input{
  870. width: 600px;
  871. margin-left: 20px;
  872. margin-top: -5px;
  873. }
  874. /* 确认按钮样式 */
  875. .confirm-button{
  876. background-color: #FF0066;
  877. color: white;
  878. width:300px;
  879. margin-left: 160px;
  880. }
  881. /* 追加按钮样式(この上に設問を追加/この下に設問を追加按钮) */
  882. .append-button{
  883. background-color: #FF0066;
  884. color: white;
  885. }
  886. /* 表单容器整体样式 */
  887. .form-container {
  888. margin-top: 40px;
  889. }
  890. /* 段落样式调整 */
  891. p {
  892. margin-bottom: 10px;
  893. }
  894. /* 表单区块标题样式(公開期間、エリア等标题) */
  895. .form-section {
  896. font-size: 14px;
  897. font-weight: bold;
  898. margin: -10px 0 5px 27px;
  899. }
  900. /* 垂直排列的单选框组容器样式 */
  901. .radio-group-vertical {
  902. margin-left: 65px;
  903. }
  904. /* 移动端垂直排列的单选框组容器样式适配 */
  905. @media (max-width:48em) {
  906. .radio-group-vertical {
  907. margin-left: 7%;
  908. }
  909. }
  910. /* 单选框容器布局控制 */
  911. .radio-container {
  912. display: flex;
  913. flex-direction: column;
  914. gap: 12px;
  915. }
  916. /* 即時公開单选框样式 */
  917. .vertical-radio {
  918. display: flex;
  919. align-items: center;
  920. }
  921. /* 期間指定单选框样式 */
  922. .scheduled-radio{
  923. display: flex;
  924. align-items: center;
  925. margin-top: -15px;
  926. }
  927. /* 日期选择区域布局 */
  928. .scheduled-row {
  929. display: flex;
  930. align-items: center;
  931. margin-left: 88px;
  932. margin-top: -45px;
  933. }
  934. /* 移动端日期选择区域布局适配 */
  935. @media (max-width:48em) {
  936. .scheduled-row {
  937. margin-left: 13%;
  938. width: 80%;
  939. }
  940. }
  941. /* 日期选择器组合样式 */
  942. .date-group {
  943. display: flex;
  944. align-items: center;
  945. gap: 8px;
  946. margin-left: 8px;
  947. }
  948. /* 年份选择器宽度 */
  949. .date-select {
  950. min-width: 80px;
  951. }
  952. /* 月/日选择器宽度 */
  953. .date-select-small {
  954. min-width: 60px;
  955. }
  956. /* 日期单位文本样式(年/月/日) */
  957. .date-label {
  958. font-size: 14px;
  959. color: #666;
  960. }
  961. /* 地区选择区域布局 */
  962. .area-header-row {
  963. display: flex;
  964. align-items: flex-start;
  965. }
  966. /* 地区标题与选择器间距 */
  967. .area-header-row h1 {
  968. margin: -8px 20px 0 0;
  969. }
  970. /* 地区标题样式 */
  971. .area-title {
  972. font-size: 14px;
  973. margin-left: 57px !important;
  974. }
  975. /* 移动端地区标题适配 */
  976. @media (max-width:48em) {
  977. .area-title {
  978. margin-left:13% !important;
  979. }
  980. }
  981. /* 回答类型单选框组布局 */
  982. .custom-radio-group {
  983. display: flex;
  984. flex-wrap: wrap;
  985. align-items: center;
  986. margin-left: 35px !important;
  987. }
  988. /* 前4个回答类型单选框间距 */
  989. .custom-radio-group > .el-radio:nth-child(-n+4) {
  990. margin-right: 20px;
  991. }
  992. /* 第5个回答类型单选框(数字選択)强制换行 */
  993. .custom-radio-group > .el-radio:nth-child(5) {
  994. width: 100%;
  995. }
  996. /* 50文字输入框样式 */
  997. .textarea-input{
  998. width: 600px;
  999. margin-left: 105px !important;
  1000. margin-top: -20px;
  1001. }
  1002. /* 100文字输入框样式 */
  1003. .textarea-input100{
  1004. width: 600px;
  1005. margin-left: 105px !important;
  1006. margin-top: -40px;
  1007. }
  1008. /* 复选框选项行布局 */
  1009. .checkbox-row {
  1010. display: flex;
  1011. flex-direction: row;
  1012. align-items: center;
  1013. width: 100%;
  1014. }
  1015. /* 复选框与输入框间距 */
  1016. .checkbox-item {
  1017. margin-right: 8px;
  1018. }
  1019. /* 单选框样式(隐藏原生标签) */
  1020. .hidden-label-radio {
  1021. margin-right: 8px;
  1022. }
  1023. .hidden-label-radio .el-radio__label {
  1024. display: none !important;
  1025. }
  1026. /* 复选框选项输入框宽度 */
  1027. .input-item {
  1028. margin: 0 8px;
  1029. width: 15%;
  1030. }
  1031. /* 移动端复选框输入框适配 */
  1032. @media (max-width:48em) {
  1033. .input-item {
  1034. width: 60%;
  1035. }
  1036. }
  1037. /* 单选框选项输入框宽度 */
  1038. .radio-item{
  1039. width: 15%;
  1040. }
  1041. /* 移动端单选框输入框适配 */
  1042. @media (max-width:48em) {
  1043. .radio-item {
  1044. width: 60%;
  1045. }
  1046. }
  1047. /* 复选框删除按钮样式 */
  1048. .delete-btn {
  1049. color: #409eff;
  1050. text-decoration: underline;
  1051. }
  1052. /* 单选框删除按钮样式 */
  1053. .radio-btn{
  1054. margin-left: 8px;
  1055. color: #409eff;
  1056. text-decoration: underline;
  1057. }
  1058. /* 选项追加按钮样式 */
  1059. .append-btn {
  1060. display: inline-block;
  1061. background-color: black;
  1062. color: white;
  1063. margin-left:18%;
  1064. }
  1065. /* 移动端追加按钮适配 */
  1066. @media (max-width:48em) {
  1067. .append-btn {
  1068. margin-left:80%;
  1069. }
  1070. }
  1071. /* 地区选择树样式 */
  1072. .region-tree-container {
  1073. margin-top: -12px;
  1074. margin-left: -2px;
  1075. }
  1076. /* 業種复选框组布局 */
  1077. .el-form-item__businessTypeCode{
  1078. margin-left: 55px;
  1079. display: flex;
  1080. align-items: center;
  1081. margin-top: -15px;
  1082. }
  1083. /* 移动端業種复选框组适配 */
  1084. @media (max-width:48em) {
  1085. .el-form-item__businessTypeCode {
  1086. margin-left:13%;
  1087. }
  1088. }
  1089. /* 数字选择器单位单选框组样式 */
  1090. .mx-radio-group{
  1091. margin-left: 60px;
  1092. min-width: 120px;
  1093. }
  1094. /* 移动端数字选择器单位适配 */
  1095. @media (max-width:48em) {
  1096. .mx-radio-group {
  1097. margin-left: 45px;
  1098. min-width: 110px;
  1099. }
  1100. }
  1101. /* 单位单选框间距 */
  1102. .radio-group{
  1103. margin-right: 50px;
  1104. }
  1105. /* 移动端单位单选框间距适配 */
  1106. @media (max-width:48em) {
  1107. .radio-group {
  1108. margin-right: 10px;
  1109. }
  1110. }
  1111. </style>