Merge branch 'feature/feature-20250331-h5' of https://e.coding.yili.com/yldc/ylst/ylst-survey-h5 into feature/feature-20250331-h5

This commit is contained in:
Huangzhe
2025-03-12 18:37:49 +08:00
39 changed files with 1907 additions and 394 deletions

3
.env
View File

@@ -8,3 +8,6 @@ VITE_APP_MESSAGE_CENTER=http://gtech-gateway.dcin-test.digitalyili.com/apigtech/
VITE_APP_SOCKETURL=wss://yls-api-uat.dctest.digitalyili.com/survey_sync
VITE_APP_JSONPURL=https://iam-uat.dctest.digitalyili.com/idp/restful/getIDPToken
VITE_APP_YQRURL=https://ocp-uat-ain.digitalyili.com
# VITE_APP_BASE_APPURL=https://ycsb-gw-uat.dcin-test.digitalyili.com
# VITE_APP_APPKEY=62f495a0f7854e4e46ebbf40
# VITE_APP_APPID=m5c66hlce3

View File

@@ -8,3 +8,6 @@ VITE_APP_MESSAGE_CENTER=http://gtech-gateway.dcin-test.digitalyili.com/apigtech/
VITE_APP_SOCKETURL=wss://yls-api-uat.dctest.digitalyili.com/survey_sync
VITE_APP_JSONPURL=https://iam-uat.dctest.digitalyili.com/idp/restful/getIDPToken
VITE_APP_YQRURL=https://ocp-uat-ain.digitalyili.com
# VITE_APP_BASE_APPURL=https://ycsb-gw-uat.dcin-test.digitalyili.com
# VITE_APP_APPKEY=62f495a0f7854e4e46ebbf40
# VITE_APP_APPID=m5c66hlce3

4
auto-imports.d.ts vendored
View File

@@ -5,6 +5,4 @@
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
}
declare global {}

8
components.d.ts vendored
View File

@@ -14,23 +14,16 @@ declare module 'vue' {
VanButton: typeof import('vant/es')['Button']
VanCell: typeof import('vant/es')['Cell']
VanCellGroup: typeof import('vant/es')['CellGroup']
VanCheck: typeof import('vant/es')['Check']
VanCheckbo: typeof import('vant/es')['Checkbo']
VanCheckbox: typeof import('vant/es')['Checkbox']
VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
VanCol: typeof import('vant/es')['Col']
VanDatePicker: typeof import('vant/es')['DatePicker']
VanDatetimePicker: typeof import('vant/es')['DatetimePicker']
VanDialog: typeof import('vant/es')['Dialog']
VanDivider: typeof import('vant/es')['Divider']
VanFiel: typeof import('vant/es')['Fiel']
VanField: typeof import('vant/es')['Field']
VanGrid: typeof import('vant/es')['Grid']
VanGridItem: typeof import('vant/es')['GridItem']
VanIcon: typeof import('vant/es')['Icon']
VanNavBar: typeof import('vant/es')['NavBar']
VanPicker: typeof import('vant/es')['Picker']
VanPikcer: typeof import('vant/es')['Pikcer']
VanPopup: typeof import('vant/es')['Popup']
VanRadio: typeof import('vant/es')['Radio']
VanRadioGroup: typeof import('vant/es')['RadioGroup']
@@ -40,7 +33,6 @@ declare module 'vue' {
VanSwitch: typeof import('vant/es')['Switch']
VanTabbar: typeof import('vant/es')['Tabbar']
VanTabbarItem: typeof import('vant/es')['TabbarItem']
VanTimePicker: typeof import('vant/es')['TimePicker']
YLPicker: typeof import('./src/components/YLPicker.vue')['default']
YLSelect: typeof import('./src/components/YLSelect.vue')['default']
}

View File

@@ -1,5 +1,19 @@
<script setup lang="ts">
import { RouterView } from 'vue-router';
import { onMounted } from 'vue';
import appBridge from '@/assets/js/appBridge';
import utils from '@/assets/js/common';
onMounted(async() => {
if (utils.getParameter('digitalYiliToken')) {
// 隐藏/显示 header
appBridge.setHeaderShown(false);
// 设置系统状态栏明暗主题
appBridge.setStatusBarStyle('light');
// 设置禁止原生返回
// appBridge.takeOverAndroidBack();
}
});
</script>
<template>

10
src/api/common/index.js Normal file
View File

@@ -0,0 +1,10 @@
import request from '@/utils/request.js';
// 校验token返回用户信息
export function getUserInfo(params) {
return request({
url: '/thirdPartyInterfaceInfoEx/test',
method: 'get',
params
});
}

View File

@@ -19,7 +19,7 @@ export function sync(params) {
method: 'get'
});
}
export function questionDetails(params) {
export function saveQuestions(params) {
// let sn = params.sn;
// delete params.sn;
return request({

View File

@@ -17,3 +17,18 @@ export function consoleSurveys(params) {
}
});
}
export function getListScene(params) {
return request({
url: 'console/list_scene',
method: 'get',
params
});
}
//
export function getSurveyTemplates(params) {
return request({
url: '/console/survey_templates',
method: 'get',
params
});
}

34
src/api/survey/index.js Normal file
View File

@@ -0,0 +1,34 @@
import request from '@/utils/request';
// 投放问卷
export function publishSurvey(data) {
return request({
url: `/console/survey_publishes`,
method: 'post',
data
});
}
// 问卷详情
export function getSurveyInfo(sn) {
return request({
url: `/console/surveys/${sn}`,
method: 'get'
});
}
// 查看二维码
export function getQrcode(sn) {
return request({
url: `/console/survey_publishes/${sn}/qrcode`,
method: 'get'
});
}
// 获取问卷测试是否弹层
export function getCheckSurvey(sn) {
return request({
url: `/console/check_survey_test/${sn}`,
method: 'get'
});
}

289
src/assets/js/appBridge.js Normal file
View File

@@ -0,0 +1,289 @@
export default {
/** ****************** ReactNative通信 ********************/
/**
* 生成唯一ID
* @returns {string} 唯一ID
*/
generateUniqueId() {
return `cb_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
},
/**
* @desc 检查是否在ReactNative环境中
* @returns {Boolean} 是否在ReactNative WebView中
*/
isInReactNative() {
return typeof window !== 'undefined' && !!window.ReactNativeWebView;
},
/**
* @desc 发送消息到ReactNative
* @param {Object} message 消息对象
* @returns {Boolean} 发送是否成功
*/
postMessageToRN(data) {
if (!this.isInReactNative()) {
return false;
}
try {
window.ReactNativeWebView.postMessage(JSON.stringify(data));
return true;
} catch (error) {
return false;
}
},
/**
* @desc 控制头部导航栏显示/隐藏
* @param {Boolean} visible 是否显示导航栏
* @returns {Boolean} 操作是否成功
*/
setHeaderShown(visible = false) {
return this.postMessageToRN({
type: 'headerShown',
visible
});
},
/**
* @desc 关闭当前页面,触发返回操作
* @returns {Boolean} 操作是否成功
*/
navigateBack() {
return this.postMessageToRN({
type: 'back'
});
},
/**
* @desc 设置头部导航栏标题
* @param {String} title 标题文本
* @returns {Boolean} 操作是否成功
*/
setTitle(title) {
if (title) {
return this.postMessageToRN({
type: 'title',
title
});
}
},
/**
* @desc 设置系统状态栏明暗主题
* @param {String} style 主题风格,'light'为浅色,'dark'为深色
* @returns {Boolean} 操作是否成功
*/
setStatusBarStyle(style = 'light') {
return this.postMessageToRN({
type: 'statusBar',
style
});
},
/**
* @desc 跳转到APP页面
* @param {String} url APP内页面路由
* @returns {Boolean} 操作是否成功
*/
navigateToAppPage(url) {
return this.postMessageToRN({
type: 'navigate',
url
});
},
/**
* @desc 跳转到H5页面
* @param {String} h5Url 目标H5链接
* @param {String} name 导航头中的标题文案
* @param {Boolean} fullscreen 是否全屏显示(隐藏导航头)
* @returns {Boolean} 操作是否成功
*/
navigateToH5(h5Url, name, fullscreen = false) {
if (fullscreen) {
h5Url = this.appendQueryParam(h5Url, 'fullscreen', 'true');
}
return this.postMessageToRN({
type: 'navigate',
url: `authH5?url=${encodeURIComponent(h5Url)}&name=${name}`
});
},
// /**
// * @desc 给URL添加查询参数
// * @param {String} url 原始URL
// * @param {String} key 参数名
// * @param {String} value 参数值
// * @returns {String} 添加参数后的URL
// * @private
// */
// appendQueryParam(url, key, value) {
// const separator = url.includes('?') ? '&' : '?';
// return `${url}${separator}${key}=${value}`;
// },
/**
* @desc 禁用原生返回Android
* @returns {Boolean} 操作是否成功
*/
takeOverAndroidBack() {
return this.postMessageToRN({
type: 'takeOverAndroidBack'
});
},
/**
* @desc 跳转到手机浏览器中打开页面
* @param {String} url 要打开的URL
* @returns {Boolean} 操作是否成功
*/
openInBrowser(url) {
return this.postMessageToRN({
type: 'openLink',
url
});
},
/**
* @desc 使用原生扫码工具
* @param {Function} callback 扫码结果回调函数
* @returns {Boolean} 操作是否成功
*/
scanCode(callback) {
const cbId = this.generateUniqueId();
window[cbId] = callback;
return this.postMessageToRN({
type: 'scanCode',
cbId
});
},
/**
* @desc 分享链接到微信
* @param {Object} options 分享选项
* @param {String} options.title 分享标题
* @param {String} options.description 分享副标题
* @param {String} options.thumbImageUrl 缩略图URL
* @param {String} options.webpageUrl 网页链接
* @param {Number} options.scene 分享场景0为微信好友1为朋友圈
* @param {Function} callback 分享结果回调函数
* @returns {Boolean} 操作是否成功
*/
shareToWeChat(options, callback) {
const cbId = this.generateUniqueId();
window[cbId] = callback;
return this.postMessageToRN({
type: 'shareToWx',
title: options.title,
description: options.description,
thumbImageUrl: options.thumbImageUrl,
webpageUrl: options.webpageUrl,
scene: options.scene || 0,
cbId
});
},
/**
* @desc 分享视频到抖音
* @param {Object} options 分享选项
* @param {String} options.title 分享标题
* @param {String} options.shortTitle 抖音短标题
* @param {Array<String>} options.videos 视频地址数组
* @param {Boolean} options.isPublish 是否直接跳转到发布页
* @param {Boolean} options.tip 是否显示APP自带的提示与loading
* @param {Function} callback 分享结果回调函数
* @returns {Boolean} 操作是否成功
*/
shareVideoToDouyin(options, callback) {
const cbId = this.generateUniqueId();
window[cbId] = callback;
return this.postMessageToRN({
type: 'shareVideoToDouyin',
title: options.title,
shortTitle: options.shortTitle,
videos: options.videos,
isPublish: options.isPublish !== false,
tip: options.tip !== false,
cbId
});
},
/**
* @desc 唤起抖音
* @param {Function} callback 操作结果回调函数
* @returns {Boolean} 操作是否成功
*/
openDouyin(callback) {
const cbId = this.generateUniqueId();
window[cbId] = callback;
return this.postMessageToRN({
type: 'openDouyin',
cbId
});
},
/**
* @desc 分享文本到微博
* @param {String} content 要分享的文本内容
* @param {Function} callback 分享结果回调函数
* @returns {Boolean} 操作是否成功
*/
shareTextToWeibo(content, callback) {
const cbId = this.generateUniqueId();
window[cbId] = callback;
return this.postMessageToRN({
type: 'shareTextToWeibo',
content,
cbId
});
},
/**
* @desc 保存到相册
* @param {String} src 媒体资源URL图片或视频
* @param {Function} callback 保存结果回调函数
* @returns {Boolean} 操作是否成功
*/
save2Album(src, callback) {
const cbId = this.generateUniqueId();
window[cbId] = callback;
return this.postMessageToRN({
type: 'save2Album',
src,
cbId
});
},
/**
* @desc 请求安卓权限
* @param {String} permission 权限名称,如'android.permission.CAMERA'
* @param {Function} callback 请求结果回调函数
* @returns {Boolean} 操作是否成功
*/
requestAndroidPermission(permission, callback) {
const cbId = this.generateUniqueId();
window[cbId] = callback;
return this.postMessageToRN({
type: 'androidPermission',
permission,
cbId
});
},
/**
* @desc 获取新的认证令牌
* @param {Function} callback 获取结果回调函数,参数格式为:
* {status: 0|1, msg: string, data: string}
* status: 0成功1失败
* msg: 错误信息
* data: 新token
* @returns {Boolean} 操作是否成功
*/
getNewAuthToken(callback) {
const cbId = this.generateUniqueId();
window[cbId] = callback;
return this.postMessageToRN({
type: 'newAuthToken',
cbId
});
}
};

165
src/assets/js/common.js Normal file
View File

@@ -0,0 +1,165 @@
export default {
/** ****************** 数据转换工具 ********************/
/**
* @desc json转string
* @param {*} value 值
* @return {*} value 值
*/
_json2string(value) {
return JSON.stringify(value);
},
/**
* @desc string转json
* @param {*} value 值
* @return {*} value 值
*/
_string2json(value) {
try {
return JSON.parse(value);
} catch (e) {
// 以后再说
}
},
/**
* @desc 对象解除关联(深拷贝)
* @param {*} obj
*/
convertObj(obj) {
return JSON.parse(JSON.stringify(obj));
},
/** ****************** Cookie操作 ********************/
/**
* @desc 获取cookie
* @param {String} name 名称
* @return {*} 值
*/
getCookie(name) {
let rs = '';
const nameStr = `${name}=`;
const ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(nameStr) !== -1) {
rs = this._string2json(c.substring(nameStr.length, c.length));
}
}
return rs;
},
/**
* @desc 设置cookie
* @param {String} name 名称
* @param {*} value 值
* @param {Number} hours 过期时间(小时)
*/
setCookie(name, value, hours) {
let str = `${name}=${this._json2string(value)}`;
if (hours && hours > 0) {
const date = new Date();
date.setTime(date.getTime() + hours * 3600 * 1000);
str += `; expires=${date.toUTCString()}`;
}
document.cookie = str;
},
/**
* @desc 删除cookie
* @param {String} name 名称
* @param {Object} options 选项
* @param {Boolean} options.usePath 是否指定路径
*/
delCookie(name, options = {}) {
const date = new Date();
date.setTime(date.getTime() - 10000);
let cookieStr = `${name}=a; expires=${date.toGMTString()}`;
if (options.usePath) {
cookieStr += ';path=/micro;';
}
document.cookie = cookieStr;
},
/**
* @desc 删除cookie带路径
* @param {String} name 名称
*/
clearCookie(name) {
this.delCookie(name, { usePath: true });
},
/** ****************** 本地存储操作 ********************/
/**
* @desc 获取localStorage中指定的变量
* @param {String} name 名称
* @return {*} 值
*/
getStorage(name) {
return this._string2json(window.localStorage[name]);
},
/**
* @desc 设置localStorage中指定的变量
* @param {String} name 名称
* @param {*} value 值
*/
setStorage(name, value) {
window.localStorage[name] = this._json2string(value);
},
/**
* @desc 删除localStorage中指定的变量
* @param {String} name 名称
*/
delStorage(name) {
window.localStorage.removeItem(name);
},
/**
* @desc 获取sessionStorage中指定的变量
* @param {String} name 名称
* @return {*} 值
*/
getSessionStorage(name) {
return this._string2json(window.sessionStorage.getItem(name));
},
/**
* @desc 设置sessionStorage中指定的变量
* @param {String} name 名称
* @param {*} value 值
*/
setSessionStorage(name, value) {
window.sessionStorage.setItem(name, this._json2string(value));
},
/**
* @desc 删除sessionStorage中指定的变量
* @param {String} name 名称
*/
delSessionStorage(name) {
window.sessionStorage.removeItem(name);
},
/** ****************** URL工具 ********************/
/**
* @desc 获取URL中的参数
* @param {String} name 参数名
* @return {String} 参数值
*/
getParameter(name) {
const reg = new RegExp(`${name}=.*`, 'g');
const str = window.location.href.match(reg);
if (str) {
return str[0].split('&')[0].split('=')[1];
} else {
return '';
}
}
};

View File

@@ -167,8 +167,8 @@ const getMaxDateLimit = computed(() => {
props.format
);
const tempStr = '0000-12-31 23:59:59';
const result =
props.maxDate.length !== 0 && thisMax.length > props.maxDate.length
const result
= props.maxDate.length !== 0 && thisMax.length > props.maxDate.length
? thisMax.slice(0, props.maxDate.length) + tempStr.slice(props.maxDate.length)
: thisMax;
return result.slice(0, props.format.length);
@@ -191,8 +191,8 @@ function onChange({ selectedValues, columnIndex }) {
renderMinuteColumns,
renderSecondColumns
];
updateColumns[columnIndex] &&
updateColumns[columnIndex](changeValue, getMinDateLimit.value, getMaxDateLimit.value, false);
updateColumns[columnIndex]
&& updateColumns[columnIndex](changeValue, getMinDateLimit.value, getMaxDateLimit.value, false);
}
// 渲染全部列

View File

@@ -9,4 +9,6 @@ export default {
jsonpUrl: import.meta.env.VITE_APP_JSONPURL,
jqrUrl: import.meta.env.VITE_APP_YQRURL,
currentMode: import.meta.env.VITE_APP_CURRENTMODE
// appKey: import.meta.env.VITE_APP_APPKEY,
// appId: import.meta.env.VITE_APP_APPID
};

View File

@@ -6,6 +6,11 @@ import App from './App.vue';
import router from './router';
// 2. 引入组件样式
import 'vant/lib/index.css';
import appBridge from '@/assets/js/appBridge';
router.beforeEach((to, from, next) => {
appBridge.setTitle(to.meta.title as string);
next();
});
const app = createApp(App);
app.use(createPinia());
app.use(router);

View File

@@ -51,15 +51,17 @@ const router = createRouter({
name: 'preview',
meta: {},
component: Preview
}, {
},
{
path: '/create',
name: 'create',
meta: { title: '问卷编辑' },
component: () => import('../views/Survey/views/Create/Index.vue')
}, {
},
{
path: '/publish',
name: 'publish',
meta: { title: '问卷发布' },
meta: { title: '问卷投放' },
component: () => import('../views/Survey/views/Publish/Index.vue')
}
]

View File

@@ -9,19 +9,20 @@ export const useCommonStore = defineStore('common', {
state: () => ({
questionsInfo: {
survey: {
id: 9577,
introduction: '<p>123</p>',
pages: [[]],
sn: 'oxywX8W6',
id: 9616,
introduction:
'<p><b>为优化活动服务品质,烦请完成问卷,感谢配合!您的反馈至关重要!(此提示语为默认提示语,您可选择自行输入本问卷的提示语)</b></p>',
pages: [[7], [23], [24], [25], [26], [9], [13], [12], [11], [10]],
sn: 'qxj894Ww',
status: 0,
title: '报名签到问卷 ',
detail_pages: [],
title: '<b>报名签到问卷 </b>',
detail_pages: [[7], [23], [24], [25], [26], [9], [13], [12], [11], [10]],
group_pages: [],
is_one_page_one_question: 0,
last_question_index: 0,
is_one_page_one_question: 1,
last_question_index: 26,
is_three_d_permissions: 0,
is_dept: 1,
last_title: 'Q0',
last_title: 'Q4',
project_name: '报名签到问卷 ',
quota_end_content:
'<p style="text-align:center"><img style="width:220px;margin-top:30px;margin-bottom: 40px;" src="https://cxp-pubcos.yili.com/prod-yls/theme/XxgQ98WN/1693807609602_962_error.png"></p>\n<p style="text-align:center;font-size: 16px;font-weight: 500;padding-bottom: 12px;/* margin-bottom: 10px; */">很抱歉,本次调研不太适合您的情况,感谢您的参与!</p>',
@@ -37,14 +38,369 @@ export const useCommonStore = defineStore('common', {
end_jump_status: 0,
end_jump_standing_time: 0,
success_end_content:
'<p style="text-align:center"><img style="width:220px;margin-top:30px;margin-bottom: 40px;" src="https://cxp-pubcos.yili.com/prod-yls/theme/XxgQ98WN/1693807609607_514_success.png"></p>\n<p style="text-align:center;font-size: 16px;font-weight: 500;padding-bottom: 12px;/* margin-bottom: 10px; */">您已完成本次调研,感谢您的参与!</p>\n<p style="text-align:center;color: 85b43a;font-size: 16px;font-weight: 550;">【成功完成】</p>',
'<p style="text-align:center"><img style="width:220px;margin-top:30px;margin-bottom: 40px;" src="https://cxp-pubcos.yili.com/prod-yls/theme/XxgQ98WN/1693807609607_514_success.png"></p>\n<p style="text-align:center;font-size: 16px;font-weight: 500;padding-bottom: 12px;/* margin-bottom: 10px; */">您已完成本次调研,感谢您的参与!</p>\n<p style="text-align:center;color: #85b43a;font-size: 16px;font-weight: 550;">【成功完成】</p>',
success_end_url: '',
success_end_url_select: 0,
success_standing_time: 0,
template_type: 0,
local_pages: []
local_pages: [
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
logics: [],
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
},
{
pages: [],
is_short_time: 0,
short_time: '',
is_show: 0,
use_type: 0
}
]
},
logics: [
{
id: 472233,
question_index: 10,
sample_number: 0,
skip_question_index: -1,
skip_type: 0,
question_id: '17853822',
logic: [
{
value: '',
location: 0,
date: '',
time: '',
type: 0,
row_type: 0,
cell_type: 0,
logic: 'if',
operator: '=',
is_answer: 1,
is_select: 0,
row_index: 0,
cell_index: 0,
question_type: 1,
question_index: 7,
relation_question_index: 0,
relation_question_row_index: 0,
relation_question_cell_index: 0,
is_option_group: 0,
option_index: 1,
skip_type: null,
question_id: null
}
],
autofill: {
value: '',
date: '',
time: '',
question_type: 1,
option_indexs: [],
row_indexs: [],
cell_indexs: []
},
hide_option_index: []
},
{
id: 472236,
question_index: 10,
sample_number: 0,
skip_question_index: 7,
skip_type: 0,
question_id: '17853822',
logic: [
{
value: '',
location: 0,
date: '',
time: '',
type: 0,
row_type: 0,
cell_type: 0,
logic: 'if',
operator: '=',
is_answer: 1,
is_select: 1,
row_index: 0,
cell_index: 0,
question_type: 2,
question_index: 7,
relation_question_index: 0,
relation_question_row_index: 0,
relation_question_cell_index: 0,
is_option_group: 0,
option_index: 1,
skip_type: null,
question_id: null
}
],
autofill: {
value: '',
date: '',
time: '',
question_type: 1,
option_indexs: [],
row_indexs: [],
cell_indexs: []
},
hide_option_index: []
},
{
id: 472237,
question_index: 10,
sample_number: 0,
skip_question_index: 13,
skip_type: 0,
question_id: '17853822',
logic: [
{
value: '',
location: 0,
date: '',
time: '',
type: 0,
row_type: 1,
cell_type: 2,
logic: 'if',
operator: '=',
is_answer: 1,
is_select: 0,
row_index: 2,
cell_index: 3,
question_type: 9,
question_index: 24,
relation_question_index: 0,
relation_question_row_index: 0,
relation_question_cell_index: 0,
is_option_group: 0,
option_index: 0,
skip_type: null,
question_id: null
}
],
autofill: {
value: '',
date: '',
time: '',
question_type: 1,
option_indexs: [],
row_indexs: [],
cell_indexs: []
},
hide_option_index: []
},
{
id: 472240,
question_index: 10,
sample_number: 0,
skip_question_index: 0,
skip_type: 0,
question_id: '17853822',
logic: [
{
value: '8',
location: 0,
date: '',
time: '',
type: 0,
row_type: 0,
cell_type: 0,
logic: 'if',
operator: '=',
is_answer: 1,
is_select: 0,
row_index: 0,
cell_index: 0,
question_type: 106,
question_index: 26,
relation_question_index: 0,
relation_question_row_index: 0,
relation_question_cell_index: 0,
is_option_group: 0,
option_index: 1,
skip_type: null,
question_id: null
}
],
autofill: {
value: '',
date: '',
time: '',
question_type: 1,
option_indexs: [],
row_indexs: [],
cell_indexs: []
},
hide_option_index: []
},
{
id: 472241,
question_index: 10,
sample_number: 0,
skip_question_index: 0,
skip_type: 0,
question_id: '17853822',
logic: [
{
value: '213',
location: 0,
date: '',
time: '',
type: 0,
row_type: 0,
cell_type: 0,
logic: 'if',
operator: '=',
is_answer: 1,
is_select: 0,
row_index: 0,
cell_index: 0,
question_type: 5,
question_index: 25,
relation_question_index: 0,
relation_question_row_index: 0,
relation_question_cell_index: 0,
is_option_group: 0,
option_index: 1,
skip_type: null,
question_id: null
}
],
autofill: {
value: '',
date: '',
time: '',
question_type: 1,
option_indexs: [],
row_indexs: [],
cell_indexs: []
},
hide_option_index: []
}
],
questions: [],
cycle_pages: null
}

12
src/utils/public.js Normal file
View File

@@ -0,0 +1,12 @@
// 根据是否是每页一体 处理成不同的结构类型
export function getPages(questions, isOnePageOneQuestion) {
const pages = [];
questions.map((item) => {
if (isOnePageOneQuestion === 1) {
pages.push([item.question_index]);
} else {
pages.push(item.question_index);
}
});
return isOnePageOneQuestion === 1 ? pages : [pages];
}

View File

@@ -15,21 +15,35 @@
:index="index"
:chooseQuestionId="chooseQuestionId"
@get-choose-question-id="getChooseQuestionId"
@move="emitFun.move"
@copy="emitFun.copy"
@delete="emitFun.delete"
@setting="emitFun.setting"
@logics="emitFun.logics"
>
<!-- 打分题 -->
<Rate
v-if="element.question_type === 5"
:element="computedElement(element)"
:index="index"
:active="chooseQuestionId === element.id"
@update:element="updateElement"
/>
<!-- 选择题 -->
<Choice
v-if="element.question_type === 1 || element.question_type === 2"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
:element="computedElement(element)"
@update:element="updateElement"
></Choice>
<!-- 填空题 -->
<Completion
v-if="element.question_type === 4"
:index="index"
:element="element"
:element="computedElement(element)"
:active="chooseQuestionId === element.id"
sn="lXEBBpE2"
@update:element="updateElement"
></Completion>
<!-- 矩阵题 -->
@@ -39,49 +53,45 @@
element.question_type === 9 ||
element.question_type === 10
"
:element="element"
:element="computedElement(element)"
:index="index"
:active="chooseQuestionId === element.id"
@update:element="updateElement"
/>
<!-- 签名题 -->
<sign-question
v-if="[22].includes(element.question_type)"
:element="element"
:element="computedElement(element)"
:index="index"
:active="chooseQuestionId === element.id"
@update:element="updateElement"
/>
<!-- 文件上传题 -->
<file-upload
v-if="element.question_type === 18"
:element="element"
:element="computedElement(element)"
:index="index"
:active="chooseQuestionId === element.id"
@update:element="updateElement"
></file-upload>
<!-- 打分题 -->
<Rate
v-if="element.question_type === 5"
:element="element"
:index="index"
:active="chooseQuestionId === element.id"
sn="lXEBBpE2"
/>
<!--图文-->
<TextWithImages
v-if="element.question_type === 6"
:element="element"
:element="computedElement(element)"
:index="index"
:active="chooseQuestionId === element.id"
@update:element="updateElement"
/>
<!--图文-->
<NPS
v-if="element.question_type === 106"
:element="element"
:element="computedElement(element)"
:index="index"
:active="chooseQuestionId === element.id"
@update:element="updateElement"
/>
<!--组件底部左侧操作-->
<template #action="{ element: el }">
@@ -103,6 +113,8 @@
</div>
</template>
</choose-question>
<!-- 增加控制按钮-->
<slot name="button" :item="element"></slot>
<!-- {{ element.question_type }}-->
<!-- {{questionInfo.survey.pages.length}}-->
@@ -121,9 +133,12 @@
</template>
<script setup>
import { v4 as uuidv4 } from 'uuid';
import { ref, onMounted, watch, computed } from 'vue';
import * as Base64 from 'js-base64';
import { ref, onMounted, watch, computed, reactive } from 'vue';
import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia';
import { useRoute } from 'vue-router';
import { saveQuestion, sync } from '@/api/design/index.js';
import Draggable from './components/Draggable.vue';
import Choice from './components/Questions/Choice.vue';
import ChooseQuestion from './components/ChooseQuestion.vue';
@@ -135,9 +150,10 @@ import TextWithImages from '@/views/Design/components/Questions/TextWithImages.v
import SignQuestion from './components/Questions/SignQuestion.vue';
import FileUpload from './components/Questions/FileUpload.vue';
import NPS from '@/views/Design/components/Questions/NPS.vue';
import { getPages } from '@/utils/public.js';
const activeIndex = ref(-1);
const route = useRoute();
// 获取所有的 question 列表内容
const { filterGap, activeId } = defineProps({
filterGap: {
@@ -151,6 +167,14 @@ const { filterGap, activeId } = defineProps({
}
});
const computedElement = computed(() => (element) => {
return reactive({
...element,
// 添加需要响应式的属性
options: element.options.map((opt) => reactive(opt))
});
});
watch(
() => activeId,
(newVal) => {
@@ -210,12 +234,25 @@ const counterStore = useCounterStore();
const store = storeToRefs(counterStore);
const chooseQuestionId = ref('');
const chooseQuestionIndex = ref(-1);
const questionInfo = computed(() => store.questionsInfo.value);
// 自动更新 题型
watch(
() => questionInfo.value.questions[chooseQuestionIndex.value],
(newVal) => {
if (newVal) {
// saveQueItem(questionInfo.value.logics, [newVal]);
}
},
{ deep: true }
);
const emit = defineEmits(['getActiveQuestion']);
// 获取选中的题目的ID
const getChooseQuestionId = (questionItem) => {
const getChooseQuestionId = (questionItem, index) => {
chooseQuestionId.value = questionItem.id;
chooseQuestionIndex.value = index;
// 向外传出选中的题目
emit('getActiveQuestion', questionItem);
};
@@ -256,6 +293,7 @@ const actionEvent = (item, el) => {
const actionFun = {
// 单选事件 添加选项
radioAddOption: (element) => {
console.log(element);
element.options.map((item) => {
item.push({
id: uuidv4(),
@@ -295,6 +333,54 @@ const actionFun = {
}
};
// emit 事件
const saveQueItem = (logics, questions, survey) => {
saveQuestion({
sn: route.query.sn,
data: {
logics: logics || [],
questions: questions || [],
survey: {
local_pages: [],
...survey,
pages: getPages(
questionInfo.value.questions,
questionInfo.value.survey.is_one_page_one_question
),
version: Base64.encode(`${new Date().getTime()}`)
}
}
}).then(() => {
sync({ sn: route.query.sn });
});
};
const emitFun = {
move: () => {
saveQueItem();
},
copy: (item) => {
saveQueItem(null, [item]);
},
delete: () => {
saveQueItem();
},
// 右下角操作
setting: (item) => {
saveQueItem(null, [item]);
},
logics: (item) => {
saveQueItem(questionInfo.value.logics, [item]);
}
};
// 直接切割 变更为响应式
const updateElement = (newElement) => {
const index = questionInfo.value.questions.findIndex((q) => q.id === newElement.id);
if (index !== -1) {
questionInfo.value.questions.splice(index, 1, newElement);
}
saveQueItem(questionInfo.value.logics, [newElement]);
};
onMounted(() => {
questionInfo.value = store.questionsInfo.value;
});

View File

@@ -1,40 +1,15 @@
<script setup lang="ts">
import PreviewIndex from './Index.vue';
import { useCommonStore } from '@/stores/modules/common';
import { storeToRefs } from 'pinia';
import { ref } from 'vue';
import Success from '@/views/Survey/views/Success/Index.vue';
const { getQuestionsInfo: questionsInfo } = storeToRefs(useCommonStore());
// 是否提交?如果提交则把组件展示出来
const isSubmit = ref(true);
</script>
<template>
<van-nav-bar title="预览" left-arrow>
<!-- <template #right>-->
<!-- <van-icon name="search" />-->
<!-- </template>-->
<template #right>
<van-icon name="search" />
</template>
</van-nav-bar>
<div class="survey-preview">
<div v-if="isSubmit" style="display: flex; flex-flow: column nowrap;align-items: center;">
<div style=" width: 97%;background: white; ">
<div v-html="questionsInfo?.survey?.title"></div>
<p>为优化活动服务品质烦请完成问卷感谢配合您的反馈至关重要</p>
</div>
<preview-index :preview="true"></preview-index>
<van-button color="#70b936" style=" width: 100px;height: 30px" @click="isSubmit = !isSubmit">提交</van-button>
</div>
<!-- 提交成功展示页面 -->
<Component :is="Success" v-else />
</div>
<preview-index :filterGap="true"></preview-index>
</template>
<style lang="scss" scoped>
.survey-preview{
padding: 15px 0;
background: linear-gradient(0deg, rgba(245,245,245,1) 80%, rgba(112,185,54,0.5) 100%) white;
}
</style>
<style lang="scss" scoped></style>

View File

@@ -19,6 +19,7 @@
size="0.5rem"
:active-value="1"
:inactive-value="0"
@change="saveSettings"
></van-switch>
</template>
</van-cell>
@@ -34,6 +35,7 @@
size="0.5rem"
:active-value="1"
:inactive-value="0"
@change="saveSettings"
></van-switch>
</template>
</van-cell>
@@ -50,15 +52,25 @@
</van-cell>
<van-divider></van-divider>
<!-- 根据不同题型 展示不同的操作-->
<!-- 多选-->
<checkbox-question-action
v-if="activeQuestion.question_type === 2"
v-model="activeQuestion"
@save-option="saveSettings"
></checkbox-question-action>
<rate-action
<!-- 打分-->
<rate-question-action
v-if="activeQuestion.question_type === 5"
:config="activeQuestion.config"
@update-config="updateConfig"
></rate-action>
></rate-question-action>
<!-- 填空-->
<completion-question-action
v-if="activeQuestion.question_type === 4"
v-model="activeQuestion"
@save-option="saveSettings"
></completion-question-action>
</van-cell-group>
</van-action-sheet>
<!-- 移动 复制-->
@@ -86,6 +98,7 @@
close-icon="close"
round
:style="{ minHeight: '50%', maxHeight: '75%' }"
@close="saveLogics"
>
<div class="mv10">
<header>
@@ -105,7 +118,8 @@ import { ref } from 'vue';
import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia';
import QuestionBefore from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/QuestionBefore.vue';
import RateAction from './components/OptionItemAction/RateAction.vue';
import RateQuestionAction from './components/QuestionItemAction/RateQuestionAction.vue';
import CompletionQuestionAction from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/CompletionQuestionAction.vue';
import { v4 as uuidv4 } from 'uuid';
const store = useCounterStore();
const { questionsInfo } = storeToRefs(store);
@@ -132,10 +146,19 @@ const props = defineProps({
}
});
const questions = ref(props.questions);
// emit
const emit = defineEmits(['move', 'copy', 'delete', 'setting', 'logics']);
const saveSettings = () => {
emit('setting', activeQuestion.value);
};
const saveLogics = () => {
emit('logics', activeQuestion.value);
};
// 当前题目
const activeQuestion = ref(props.data);
// 设置更新
const show = ref(false);
const questionShow = ref(false);
const questionBeforeShow = ref(false);
@@ -151,7 +174,9 @@ const deleteQuestion = () => {
})
.then(() => {
// on confirm
const index = props.questionIndex;
questions.value.splice(props.questionIndex, 1);
emit('delete', index);
})
.catch(() => {
// on cancel
@@ -175,6 +200,8 @@ const questionMove = (action) => {
const temp = questions.value[props.questionIndex];
questions.value.splice(props.questionIndex, 1);
questions.value.splice(props.questionIndex + 1, 0, temp);
emit('move', 'down');
} else if (action.action === 'up') {
if (props.questionIndex === 0) {
return;
@@ -182,15 +209,21 @@ const questionMove = (action) => {
const temp = questions.value[props.questionIndex];
questions.value.splice(props.questionIndex, 1);
questions.value.splice(props.questionIndex - 1, 0, temp);
emit('move', 'up');
} else {
// 复制 题目 生成新的id 更新最新的 last index
const temp = questions.value[props.questionIndex];
questions.value.splice(props.questionIndex + 1, 0, {
const newQuestion = {
...temp,
id: uuidv4(),
question_index: questionsInfo.value.survey.last_question_index + 1
});
};
questions.value.splice(props.questionIndex + 1, 0, newQuestion);
questionsInfo.value.survey.last_question_index += 1;
emit('copy', newQuestion);
questionShow.value = false;
}
};
@@ -230,6 +263,7 @@ const questionSetting = (type) => {
const updateConfig = (value) => {
activeQuestion.value.config = { ...value };
saveSettings();
};
</script>
<style scoped lang="scss">

View File

@@ -9,6 +9,7 @@
input-align="right"
class="action-field"
placeholder="不限"
@blur="emit('saveOption')"
@update:model-value="
(value) => {
actionQuestion.config.min_select = Number(value);
@@ -27,6 +28,7 @@
placeholder="不限"
input-align="right"
class="action-field"
@blur="emit('saveOption')"
@update:model-value="
(value) => {
actionQuestion.config.max_select = Number(value);
@@ -49,7 +51,7 @@ const props = defineProps({
}
});
const emit = defineEmits(['update:modelValue']);
const emit = defineEmits(['update:modelValue', 'saveOption']);
const actionQuestion = computed({
get() {
return props.modelValue;

View File

@@ -0,0 +1,260 @@
<template>
<van-field
label="内容限制"
:border="false"
label-align="left"
input-align="right"
is-link
readonly
:value="selectText(actionQuestion.config.text_type)"
@click="selectTextTypeModel = true"
>
<template #input>
<span>{{ selectText(actionQuestion.config.text_type) }}</span>
</template>
</van-field>
<van-cell-group>
<van-field
v-if="[2].includes(actionQuestion.config.text_type)"
v-model="actionQuestion.config.decimal_few"
label="保留小数位"
type="number"
:border="false"
label-align="left"
input-align="right"
class="action-field"
placeholder="不限"
max="4"
min="0"
@blur="emit('saveOption')"
@update:model-value="
(value) => {
actionQuestion.config.line_height = Number(value);
}
"
>
<template #right-icon>
<span>位</span>
</template>
</van-field>
<van-field
v-if="[3, 4].includes(actionQuestion.config.text_type)"
v-model="actionQuestion.config.decimal_few"
label="允许输入标点符号"
type="number"
:border="false"
label-align="left"
input-align="right"
class="action-field"
label-width="8em"
placeholder="不限"
readonly
@blur="emit('saveOption')"
@update:model-value="
(value) => {
actionQuestion.config.line_height = Number(value);
}
"
>
<template #input></template>
<template #right-icon>
<van-switch
v-model="actionQuestion.config.include_mark"
:active-value="1"
:inactive-value="0"
size="0.5rem"
></van-switch>
</template>
</van-field>
</van-cell-group>
<van-divider></van-divider>
<van-cell
v-if="![5, 6, 7].includes(actionQuestion.config.text_type)"
title="字数限制"
:border="false"
label-align="left"
>
</van-cell>
<van-cell-group>
<van-field
v-if="![5, 6, 7].includes(actionQuestion.config.text_type)"
v-model="actionQuestion.config.min"
label="最少"
type="number"
:border="false"
label-align="left"
input-align="right"
class="action-field"
placeholder="不限"
@blur="emit('saveOption')"
@update:model-value="
(value) => {
actionQuestion.config.min = Number(value);
}
"
>
<template #right-icon>
<span>个</span>
</template>
</van-field>
<van-field
v-if="![5, 6, 7].includes(actionQuestion.config.text_type)"
v-model="actionQuestion.config.max"
label="最多"
type="number"
:border="false"
placeholder="不限"
input-align="right"
class="action-field"
@blur="emit('saveOption')"
@update:model-value="
(value) => {
actionQuestion.config.max = Number(value);
}
"
>
<template #right-icon>
<span>个</span>
</template>
</van-field>
</van-cell-group>
<van-cell
v-if="[0].includes(actionQuestion.config.text_type)"
title="文本类型"
:border="false"
label-align="left"
>
<template #right-icon>
<van-radio-group
v-model="actionQuestion.config.line_type"
icon-size="0.4rem"
direction="horizontal"
>
<van-radio :name="0">单行</van-radio>
<van-radio :name="1">多行</van-radio>
</van-radio-group>
</template>
</van-cell>
<van-cell-group>
<van-field
v-if="[0].includes(actionQuestion.config.text_type)"
v-model="actionQuestion.config.line_height"
label="文本高度"
type="number"
:border="false"
label-align="left"
input-align="right"
class="action-field"
placeholder="不限"
@blur="emit('saveOption')"
@update:model-value="
(value) => {
actionQuestion.config.line_height = Number(value);
}
"
>
<template #right-icon>
<span>行</span>
</template>
</van-field>
<van-field
v-model="actionQuestion.config.placeholder"
label="输入框提示"
:border="false"
label-align="left"
input-align="right"
class="action-field"
placeholder="不限"
@blur="emit('saveOption')"
@update:model-value="
(value) => {
actionQuestion.config.line_height = Number(value);
}
"
>
</van-field>
</van-cell-group>
<van-popup v-model:show="selectTextTypeModel" position="bottom" round>
<van-picker
:columns="textTypeList"
@confirm="confirm"
@cancel="selectTextTypeModel = false"
></van-picker>
</van-popup>
</template>
<script setup>
import { computed, defineEmits, ref } from 'vue';
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {};
}
}
});
const selectTextTypeModel = ref(false);
const textTypeList = [
{
text: '不限',
value: 0
},
{
text: '整数',
value: 1
},
{
text: '小数',
value: 2
},
{
text: '字母',
value: 3
},
{
text: '中文',
value: 4
},
{
text: 'email',
value: 5
},
{
text: '手机号',
value: 6
},
{
text: '身份证号',
value: 7
}
];
const emit = defineEmits(['update:modelValue', 'saveOption']);
//
const selectText = (textType) => {
return textTypeList.filter((item) => item.value === textType)[0].text;
};
const confirm = ({ selectedValues }) => {
actionQuestion.value.config.text_type = Number(selectedValues[0]);
selectTextTypeModel.value = false;
};
const actionQuestion = computed({
get() {
return props.modelValue;
},
set(newValue) {
emit('update:modelValue', newValue);
}
});
</script>
<style scoped lang="scss">
.action-field {
& ::v-deep .van-field__label {
color: #bfbfbf;
font-size: 14px;
}
}
</style>

View File

@@ -21,6 +21,7 @@
class="question"
:options="beforeQuesOptions"
placeholder="请选择问题"
@change="getQuestionType"
></yl-select>
<!-- 为空 不为空-->
<yl-select
@@ -180,13 +181,15 @@ skipOption.push(
);
// 题目选项
let optionOptions = [];
// todo 不同题型逻辑对应不同 需要开发
const changeQuestionIndex = (value, logicItem) => {
console.log(logicItem);
if (!value) {
return [];
}
beforeQuesOptions.map((item) => {
if (item.question_index === value) {
if ([1, 2].includes(logicItem.question_type)) {
if (logicItem.is_option_group === 0) {
optionOptions = item.options[0].map((optItem) => {
return {
@@ -207,6 +210,7 @@ const changeQuestionIndex = (value, logicItem) => {
optionOptions = [];
}
}
}
});
return optionOptions;
};
@@ -229,7 +233,7 @@ const addLogicItem = (logIndex, item) => {
item.splice(logIndex + 1, 0, {
logic: 'and',
question_index: '',
question_type: '',
question_type: 0,
is_answer: 1,
operator: '=',
option_index: '',
@@ -245,28 +249,16 @@ const addLogic = () => {
logics.value.push({
logic: [
{
value: '',
location: 0,
date: '',
time: '',
type: 0,
row_type: 0,
cell_type: 0,
logic: 'if',
operator: '=',
is_answer: 1,
is_select: 0,
row_index: 0,
cell_index: 0,
question_type: '',
question_index: '',
question_type: 0,
question_index: 0,
relation_question_index: 0,
relation_question_row_index: 0,
relation_question_cell_index: 0,
is_option_group: '',
option_index: '',
skip_type: null,
question_id: null
option_index: 0
}
],
skip_type: props.skipType,
@@ -323,6 +315,12 @@ const symbolOptions = [
}
];
const getQuestionType = (value) => {
console.log(beforeQuesOptions);
const type = beforeQuesOptions.filter((item) => item.question_index === value)[0];
console.log(type);
};
const logicIf = (value, index) => {
if (value === 'always') {
logics.value[index].logic = [logics.value[index].logic[0]];

View File

@@ -18,20 +18,13 @@
v-model:data="element"
:questions="questions"
:questionIndex="index"
@move="emit('move', $event)"
@copy="emit('copy', $event)"
@delete="emit('delete', $event)"
@setting="emit('setting', $event)"
@logics="emit('logics', $event)"
></question-action>
</template>
<!-- <div-->
<!-- v-for="item in questionAction"-->
<!-- :key="item.key"-->
<!-- class=""-->
<!-- :class="item.class ? item.class : ''"-->
<!-- @click="itemAction(item)"-->
<!-- >-->
<!-- <i class="icon iconfont choose-question-active-container-icon" v-html="item.icon"></i>-->
<!-- <div class="choose-question-active-container-name">-->
<!-- {{ item.name }}-->
<!-- </div>-->
<!-- </div>-->
</van-cell>
</div>
<div v-else>
@@ -70,68 +63,13 @@ const props = defineProps({
const element = ref(props.element);
// 选中题目后出现的操作
// const questionAction = ref([
// {
// icon: '&#xe630;',
// name: '编辑',
// key: 'edit',
// class: ''
// },
// {
// icon: '&#xe632;',
// name: '复制',
// key: 'copy',
// class: ''
// },
// {
// icon: '&#xe6a0;',
// name: '移动',
// key: 'moveUp',
// class: 'moverQues'
// },
// // {
// // icon:'',
// // name:'下移',
// // key:'moveDown',
// // class:''
// // },
// {
// icon: '&#xe63f;',
// name: '删除',
// key: 'delete',
// class: ''
// }
// ]);
const emit = defineEmits(['getChooseQuestionId']);
const emit = defineEmits(['getChooseQuestionId', 'move', 'copy']);
// 选中题目
const chooseItem = () => {
// 使用从 defineProps 接收的 element 对象
emit('getChooseQuestionId', props.element);
emit('getChooseQuestionId', props.element, props.index);
};
// const itemAction = (item) => {
// switch (item.key) {
// case 'edit':
// // vue router跳转到/edit
//
// router.push({
// path: '/design/edit',
// query: {
// id: props.element.id
// }
// });
// break;
// case 'copy':
// break;
// case 'moveUp':
// break;
// case 'moveDown':
// break;
// case 'delete':
// break;
// }
// };
</script>
<style scoped lang="scss">
.choose-question-container {

View File

@@ -10,7 +10,7 @@
{{ index + 1 }}
</template>
<template #label>
<contenteditable v-model="element.stem" :active="active"></contenteditable>
<contenteditable v-model="element.stem" :active="active" @blur="emitValue"></contenteditable>
</template>
<template #input>
<template v-for="(item, optionIndex) in element.options" :key="item.id">
@@ -30,12 +30,7 @@
>
<template #default>
<div class="flex align-center van-cell">
<div
class="van-cell--borderless choice-html"
:contenteditable="active"
@blur="saveOption($event, it)"
v-html="it.option"
></div>
<contenteditable v-model="it.option" :active="active"></contenteditable>
<div v-if="it.is_other">
<input class="other-input" type="text" />
</div>
@@ -61,12 +56,7 @@
>
<template #default>
<div class="flex align-center van-cell">
<div
class="van-cell--borderless choice-html"
:contenteditable="active"
@blur="saveOption($event, it)"
v-html="it.option"
></div>
<contenteditable v-model="it.option" :active="active"></contenteditable>
<div v-if="it.is_other">
<input class="other-input" type="text" />
</div>
@@ -82,7 +72,7 @@
</template>
<script setup>
import OptionAction from '@/views/Design/components/ActionCompoents/OptionAction.vue';
import { ref, defineAsyncComponent } from 'vue';
import { defineAsyncComponent, toRefs } from 'vue';
const Contenteditable = defineAsyncComponent(() => import('@/components/contenteditable.vue'));
const props = defineProps({
element: {
@@ -102,10 +92,10 @@ const props = defineProps({
default: 0
}
});
const element = ref(props.element);
const saveOption = (e, ele) => {
ele.option = e.target.innerHTML;
const { element } = toRefs(props);
const emit = defineEmits(['update:element']);
const emitValue = () => {
emit('update:element', element.value);
};
</script>
<style scoped lang="scss">

View File

@@ -10,12 +10,11 @@
{{ index + 1 }}
</template>
<template #label>
<div
:contenteditable="active"
class="van-field"
@blur="saveStem($event, element)"
v-html="element.stem"
></div>
<contenteditable
v-model="element.stem"
:active="active"
@blur="emitValue"
></contenteditable>
</template>
<template #input>
<div contenteditable="true" class="input other_input"></div>
@@ -25,7 +24,7 @@
</template>
<script setup>
import { ref } from 'vue';
import { toRefs } from 'vue';
const props = defineProps({
element: {
type: Object,
@@ -41,17 +40,14 @@ const props = defineProps({
type: Boolean,
default: false
},
sn: { type: String, default: '' },
questionType: { type: [String, Number], default: 4 }
});
const element = ref(props.element);
// 创建一个本地副本以保存更改
const localElement = ref({ ...props.element });
const emit = defineEmits(['update:element']);
const { element } = toRefs(props);
const saveStem = (e) => {
localElement.value.stem = e.target.innerHTML;
// 如果需要,可以在这里发出事件以通知父组件
// this.$emit('update:element', localElement.value);
const emitValue = () => {
emit('update:element', element.value);
};
</script>

View File

@@ -10,12 +10,11 @@
{{ index + 1 }}
</template>
<template #label>
<div
:contenteditable="active"
class="van-field"
@blur="saveStem($event, element)"
v-html="element.stem"
></div>
<contenteditable
v-model="element.stem"
:active="active"
@blur="emitValue"
></contenteditable>
</template>
<template #input>
<div v-for="(optionItem, optionItemIndex) in element.options" :key="optionItemIndex">
@@ -24,11 +23,7 @@
:key="optionIndex"
@click="chooseOption(item)"
>
<div
:contenteditable="item.id === chooseId"
class="van-field"
v-html="item.option"
></div>
<contenteditable v-model="item.option" :active="active"></contenteditable>
<RateCharacter :config="element.config"></RateCharacter>
<div class="tips">
<p>{{ element.config.prompt_left }}</p>
@@ -43,12 +38,13 @@
</template>
<script setup>
import { ref } from 'vue';
import { ref, toRefs, watch } from 'vue';
import RateCharacter from './RateCharacter.vue';
const props = defineProps({
element: {
type: Object
type: Object,
required: true
},
active: {
type: Boolean,
@@ -61,14 +57,22 @@ const props = defineProps({
sn: { type: String, default: '' },
questionType: { type: [String, Number], default: 4 }
});
const element = ref(props.element);
const chooseId = ref('');
// 创建一个本地副本以保存更改
const localElement = ref({ ...props.element });
const saveStem = (e) => {
localElement.value.stem = e.target.innerHTML;
const emit = defineEmits(['update:element']);
const chooseId = ref('');
const { element } = toRefs(props);
const emitValue = (newVal) => {
emit('update:element', newVal || element.value);
};
watch(
element.value,
(newVal) => {
emitValue(newVal);
},
{ deep: true }
);
const chooseOption = (item) => {
chooseId.value = item.id;

View File

@@ -1,12 +1,33 @@
<script setup>
// template
import LastSurvey from './components/LastSurvey/Index.vue';
import Market from './components/Market/Index.vue';
import CreateSurvey from './components/CreateSurvey/Index.vue';
import { onMounted, ref } from 'vue';
import utils from '@/assets/js/common';
import { getUserInfo } from '@/api/common/index.js';
import { showFailToast } from 'vant';
const contentShow = ref(false);
onMounted(async () => {
if (utils.getParameter('digitalYiliToken')) {
const query = {
xToken: utils.getParameter('digitalYiliToken')
};
getUserInfo(query).then((res) => {
if (res.data) {
utils.setSessionStorage('userInfo', res.data);
} else {
showFailToast(error.data?.message || error.message || '服务器错误');
}
});
} else {
contentShow.value = true;
}
});
</script>
<template>
<div class="container">
<div v-if="contentShow" class="container">
<create-survey />
<!-- 最新问卷 -->

View File

@@ -1,8 +1,7 @@
<script setup lang="ts">
// import { ref } from 'vue';
import { consoleSurveys } from '@/api/home/index.js';
import { snQuestions, questionDetails } from '@/api/design/index.js';
import { surveys } from './Hooks/useRequestHooks';
<script setup>
import { ref, onMounted } from 'vue';
import { consoleSurveys, getQuestionList } from '@/api/home/index.js';
import { snQuestions, saveQuestions } from '@/api/design/index.js';
import { useRouter } from 'vue-router';
import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia';
@@ -11,14 +10,7 @@ const counterStore = useCounterStore();
const store = storeToRefs(counterStore);
const router = useRouter();
// const surveys = ref([]);
//
// getQuestionList({}).then((res) => {
// console.log(res.data.data);
// surveys.value = res.data.data;
// });
// console.log(surveys);
const surveys = ref([]);
const createdQuestion = (item) => {
const query = {
@@ -35,7 +27,7 @@ const createdQuestion = (item) => {
if (ques.data) {
ques.data.data.survey.introduction = `<p>为优化活动服务品质,烦请完成问卷,感谢配合!您的反馈至关重要!(此提示语为默认提示语,您可选择自行输入本问卷的提示语)</p>`;
store.questionsInfo.value = ques.data.data;
questionDetails({
saveQuestions({
sn: res.data.data.sn,
introduction: ques.data.data.survey.introduction,
title: ques.data.data.survey.title
@@ -52,6 +44,28 @@ const createdQuestion = (item) => {
}
});
};
// 添加获取问卷列表的方法
const getList = () => {
getQuestionList().then((res) => {
if (res.data.code === 0) {
if (res.data.data) {
res.data.data.forEach((item) => {
if (item.parentCode && item.parentCode === 1) {
surveys.value.push(item);
}
});
surveys.value.push({});
}
}
});
};
// 在组件挂载时调用
onMounted(() => {
getList();
});
</script>
<template>
@@ -65,7 +79,7 @@ const createdQuestion = (item) => {
class="survey"
@click="createdQuestion(survey)"
>
<img width="45px" :src="survey.icon" alt=" " />
<img :src="survey.image" alt="" width="40" />
<span>{{ survey.title }}</span>
</van-col>
</van-row>

View File

@@ -1,11 +1,3 @@
<script setup lang="ts">
import { ref, h } from 'vue';
import { tables } from './hooks/useMarketHooks';
import TestComponent from './components/TestComponent.vue';
const activeComponet = ref(h(TestComponent, { cn: '报名签到' }));
</script>
<template>
<!-- 模板 -->
<div class="market">
@@ -15,15 +7,63 @@ const activeComponet = ref(h(TestComponent, { cn: '报名签到' }));
</div>
<div class="market_table">
<div v-for="item in tables" :key="item.title" @click="activeComponet = item.component">
<div v-for="item in tables" :key="item.title" @click="getMarketInfo(item)">
{{ item.title }}
</div>
</div>
<van-cell class="market_items">
<component :is="activeComponet" />
<market-item :info="marketInfo"></market-item>
<!-- <component :is="TestComponent" />-->
</van-cell>
<p class="more">-更多模板期待您的探索-</p>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import MarketItem from './components/MarketItem.vue';
import { getListScene, getSurveyTemplates } from '@/api/home';
interface SceneItem {
parentCode: number;
title: string;
component: any;
code: number;
}
const tables = ref<SceneItem[]>([]);
const marketInfo = ref([]);
const getTableList = async() => {
const res = await getListScene();
if (res.data.code === 0) {
res.data.data.forEach((item: SceneItem) => {
if (item.parentCode && item.parentCode === 1) {
tables.value.push(item);
}
});
getMarketInfo(tables.value[0]);
}
};
const getMarketInfo = async(item: SceneItem) => {
const params = {
page: 1,
per_page: 10,
group_id: 0,
is_public: 1,
scene_code_info: item.code,
sort: 'quote_nums, desc'
};
const res = await getSurveyTemplates(params);
if (res.data.code === 0) {
marketInfo.value = res.data.data;
}
};
onMounted(() => {
getTableList();
});
</script>
<style scoped>
.market {
@@ -70,5 +110,12 @@ const activeComponet = ref(h(TestComponent, { cn: '报名签到' }));
background-color: white;
}
}
.more {
margin: 10px 0;
color: #666;
font-size: 14px;
text-align: center;
}
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<van-row :gutter="12">
<van-col v-for="(item, index) in info" :key="index" :span="12" class="market-item">
<div class="content">
<div class="title">
<span>{{ item.title }}</span>
<img
src="https://files.axshare.com/gsc/DR6075/63/4d/77/634d77293a4d41d1b3d145974a8fb6a7/images/首页_1/u42.svg"
/>
</div>
<div class="desc">
<span>引用{{ item.quote_nums }}</span> |
<span>创建人: {{ item.creator }}</span>
</div>
</div>
</van-col>
</van-row>
</template>
<script setup lang="ts">
const { info } = defineProps({
info: {
type: Object,
required: true,
default: () => ({})
}
});
</script>
<style lang="scss" scoped>
.market-item {
margin-bottom: 12px;
.content {
padding: 12px;
border-radius: 8px;
background: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
img {
width: 20px;
height: 20px;
}
}
.desc {
color: #666;
font-size: 12px;
}
}
}
</style>

View File

@@ -1,36 +0,0 @@
<script setup lang="ts">
const { cn } = defineProps({
cn: {
type: String,
required: false,
deault: 'default'
}
});
</script>
<template>
<van-row :gutter="4">
<van-col
v-for="item in 2"
:key="item"
:span="10"
style="
display: flex;
flex-direction: column;
align-items: start;
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.1);
"
>
<div style="display: flex; align-items: center; justify-content: space-evenly; width: 100%">
<span>{{ cn }} 模板</span>
<img
src="https://files.axshare.com/gsc/DR6075/63/4d/77/634d77293a4d41d1b3d145974a8fb6a7/images/首页_1/u42.svg?pageId=5cc10b9f-56eb-48dc-943a-bfe7afb18a64"
/>
</div>
<div><span>引用10次</span> | <span>创建人: 张三</span></div>
</van-col>
</van-row>
</template>
<style lang="sass" scoped></style>

View File

@@ -1,4 +1,4 @@
import TestComponent from '../components/TestComponent.vue';
import TestComponent from '../components/MarketItem.vue';
import { h } from 'vue';
export const tables = [

View File

@@ -25,17 +25,23 @@
></contenteditable>
</div>
<button v-if="questionInfo.questions.length === 0" @click="show = true">添加题目</button>
<van-button v-if="questionInfo.questions.length === 0" size="small" @click="show = true">
添加题目
</van-button>
</div>
</van-cell-group>
<div class="ques">
<!-- 题目-->
<Design
:active-id="activeId"
class="desgin"
@get-active-question="getActiveQuestion"
></Design>
<Design :active-id="activeId" class="design" @get-active-question="getActiveQuestion">
<template #button="{ item }">
<div class="design-button">
<van-button v-if="activeId === item.id" size="small" @click="show = true">
添加题目
</van-button>
</div>
</template>
</Design>
<!-- <van-button @click="show = true">添加题目</van-button>-->
<!-- 弹出的新增题目弹窗-->
<van-popup
@@ -74,9 +80,9 @@
<span>投放设置</span>
</div>
<div class="survey-action_btn">
<van-button size="small">预览</van-button>
<van-button size="small">保存</van-button>
<van-button size="small" @click="$router.push({ name: 'publish' })">投放</van-button>
<van-button size="small" @click="previewQuestion">预览</van-button>
<van-button size="small" @click="saveAs">保存</van-button>
<van-button size="small" @click="publishQuestion">投放</van-button>
</div>
</div>
</div>
@@ -96,6 +102,7 @@
size="0.5rem"
:active-value="1"
:inactive-value="0"
@change="saveIsOnePage"
></van-switch>
</template>
</van-cell>
@@ -281,7 +288,7 @@ import { ref, computed, onMounted, watch } from 'vue';
import * as Base64 from 'js-base64';
import {
getSetting,
questionDetails,
saveQuestions,
saveQuestion,
snQuestions,
sync,
@@ -302,8 +309,9 @@ import {
signQuestion,
nps
} from '@/utils/importJsons';
import { useRoute } from 'vue-router';
import { useRoute, useRouter } from 'vue-router';
import YLPicker from '@/components/YLPicker.vue';
import { getPages } from '@/utils/public';
// 获取 Store 实例
const counterStore = useCounterStore();
@@ -314,6 +322,7 @@ const activeQuestionIndex = ref(-1);
const currentDate = ref();
const currentType = ref();
const route = useRoute();
const router = useRouter();
const surveyTitle = route.meta.title as string;
const show = ref(false);
// const textModel = ref(false);
@@ -344,6 +353,7 @@ const onConfirmDate = (e) => {
// 获取选中的题目
const getActiveQuestion = (activeQues) => {
chooseQuestionId.value = activeQues.id;
activeId.value = activeQues.id;
// 在questions 里 查找index 给 activeQuestionIndex
questionInfo.value.questions.forEach((item, index) => {
if (item.id === activeQues.id) {
@@ -353,7 +363,7 @@ const getActiveQuestion = (activeQues) => {
};
const saveTitle = () => {
questionDetails({
saveQuestions({
sn: route.query.sn,
title: questionInfo.value.survey.title,
introduction: questionInfo.value.survey.introduction
@@ -482,9 +492,10 @@ const saveQuestionItem = (questionJson) => {
questions: [questionJson],
survey: {
local_pages: [],
pages: questionInfo.value.questions.map((item) => {
return [item.question_index];
}),
pages: getPages(
questionInfo.value.questions,
questionInfo.value.survey.is_one_page_one_question
),
version: Base64.encode(`${new Date().getTime()}`)
}
}
@@ -520,6 +531,13 @@ const saveSetting = (parentKey, childKeys) => {
});
};
// 保存是否每页一题
const saveIsOnePage = () => {
saveQuestions({
sn: route.query.sn,
is_one_page_one_question: questionInfo.value.survey.is_one_page_one_question
});
};
const init = () => {
// event.detail 为当前输入的值
show.value = true;
@@ -565,8 +583,22 @@ watch(
},
{ deep: true }
);
// 保存 目前没有任何逻辑可以执行所有保存
const saveAs = () => {
// 保存所有
};
// 投放
const publishQuestion = () => {
router.push({ name: 'publish', query: { ...route.query } });
};
// 预览
const previewQuestion = () => {
router.push({ name: 'preview', query: { ...route.query } });
};
onMounted(async() => {
await getQuestionDetail(); // 等待接口返回数据
await getQuestionDetail();
});
</script>
@@ -598,8 +630,10 @@ onMounted(async () => {
& > button {
margin: 20px;
border-radius: 10px;
background-color: lightgreen;
//border-radius: 10px;
background-color: #70b936;
color: #fff;
}
}
}
@@ -624,8 +658,21 @@ onMounted(async () => {
font-size: 16px;
}
& .desgin {
padding-bottom: 50px;
& .design {
padding-bottom: 60px;
& .design-button {
width: 100%;
text-align: center;
::v-deep .van-button {
background-color: #70b936;
//width: 140px;
color: #fff;
font-size: 12px;
}
}
}
.ques_list {

View File

@@ -11,21 +11,20 @@
/>
</div>
<div class="qrcode">
<img src="" alt="" width="100px" height="100px" />
<div>
<img :src="publishInfo.img_url" alt="" width="100px" height="100px" />
<div class="tit">
<div>液态奶产品研究标准化问卷</div>
<div>扫码填写问卷</div>
</div>
</div>
<div>移动端仅做数据回收问卷数据分析请前往PC端浏览</div>
<div class="operation">
<span v-for="item in 2" :key="item">
<span v-for="(item, index) in operateList" :key="index" @click="operateBtn(item)">
<img
width="30px"
src="https://files.axshare.com/gsc/DR6075/44/1a/03/441a03a8b1004755a7a392b311acf97f/images/%E6%8A%95%E6%94%BE/u21.png?pageId=2f9ba10c-92b8-4c9b-b40b-04e65a0b4333"
/>
复制链接
{{ item.title }}
</span>
</div>
</van-cell-group>
@@ -42,16 +41,116 @@
height: 50px;
background-color: white;
"
>
</div>
></div>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { onMounted, reactive, ref } from 'vue';
import { showFailToast, showSuccessToast } from 'vant';
import utils from '@/assets/js/common';
import appBridge from '@/assets/js/appBridge';
import { getQrcode } from '@/api/survey';
const route = useRoute();
const surveyTitle = route.meta.title as string;
appBridge.setTitle(surveyTitle);
const operateList = reactive([
{
title: '复制链接',
type: 'copyLink'
},
{
title: '转发到微信',
type: 'shareLink'
},
{
title: '下载二维码',
type: 'qrCode'
}
]);
interface PublishInfo {
download_url: {
title: string;
};
desc?: string;
img_url: string;
url: string;
}
const publishInfo = ref<PublishInfo>({} as PublishInfo);
type OperateItem = (typeof operateList)[0];
onMounted(async () => {
getQrcode('Xxgdr5EN')
.then((res) => {
if (res.data) {
publishInfo.value = res.data.data || {};
console.log(res.data);
}
})
.catch((error) => {
showFailToast(error.data?.message || error.message || '服务器错误');
});
});
const operateBtn = (item: OperateItem) => {
switch (item.type) {
case 'shareLink':
shareLink();
break;
case 'copyLink':
copyLink();
break;
case 'qrCode':
downLoadImg();
break;
default:
break;
}
};
// 复制链接
function copyLink() {
const input = document.createElement('input');
input.value = publishInfo.value.url;
document.body.appendChild(input);
input.select();
document.execCommand('Copy');
document.body.removeChild(input);
showSuccessToast('复制成功');
}
// 分享链接
function shareLink() {
const params = {
type: 'shareToWx',
title: publishInfo.value.download_url.title,
description: publishInfo.value.desc || '',
thumbImageUrl: publishInfo.value.img_url,
webpageUrl: publishInfo.value.url,
scene: 0 // 朋友圈1 微信好友0
};
appBridge.shareToWeChat(params, () => {
// console.log('分享结果:', result);
});
}
// 下载二维码
function downLoadImg() {
const { title, url } = publishInfo.value.download_url;
if (utils.getCookie('xToken')) {
appBridge.save2Album(url, (result: any) => {
showSuccessToast('下载成功');
});
} else {
const link = document.createElement('a');
link.href = url;
link.download = title;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
</script>
<style scoped lang="scss">
@@ -76,20 +175,32 @@ const surveyTitle = route.meta.title as string;
.qrcode {
display: flex;
margin: 10px 0;
.tit{
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 10px;
line-height: 20px;
}
}
.operation {
display: flex;
flex-direction: row;
justify-content: start;
margin-right: 20px;
justify-content: space-between;
margin: 25px 10px 0 0;
span {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 10px;
img{
margin-bottom: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,69 @@
// vite.config.ts
import {
defineConfig,
loadEnv
} from 'file:///E:/yijiaofu/yili/GIT/ylst-survey-h5/node_modules/vite/dist/node/index.js';
import vue from 'file:///E:/yijiaofu/yili/GIT/ylst-survey-h5/node_modules/@vitejs/plugin-vue/dist/index.mjs';
import { fileURLToPath, URL } from 'node:url';
import vueJsx from 'file:///E:/yijiaofu/yili/GIT/ylst-survey-h5/node_modules/@vitejs/plugin-vue-jsx/dist/index.mjs';
import AutoImport from 'file:///E:/yijiaofu/yili/GIT/ylst-survey-h5/node_modules/unplugin-auto-import/dist/vite.js';
import Components from 'file:///E:/yijiaofu/yili/GIT/ylst-survey-h5/node_modules/unplugin-vue-components/dist/vite.js';
import { VantResolver } from 'file:///E:/yijiaofu/yili/GIT/ylst-survey-h5/node_modules/unplugin-vue-components/dist/resolvers.js';
import postCssPxToRem from 'file:///E:/yijiaofu/yili/GIT/ylst-survey-h5/node_modules/postcss-pxtorem/index.js';
var __vite_injected_original_import_meta_url =
'file:///E:/yijiaofu/yili/GIT/ylst-survey-h5/vite.config.ts';
var vite_config_default = defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
const proxyUrl = env.VITE_APP_BASEURL;
const proxyUrlDelivery = env.VITE_APP_DELIVERY_BASEURL;
return {
// 必须 return 配置对象
server: {
host: '0.0.0.0',
port: 3e3,
proxy: {
'/backend-api': {
target: proxyUrl,
changeOrigin: true,
pathRewrite: {
'^/backend-api': ''
// 路径重写
},
// bypass: (req) => req.headers.accept?.indexOf('html') !== -1, // 跳过 HTML 请求
cookieDomainRewrite: 'localhost'
},
'/request-java': {
target: `${proxyUrlDelivery}/api`,
changeOrigin: true,
pathRewrite: { '^/request-java': '' }
}
}
},
css: {
postcss: {
plugins: [
postCssPxToRem({
rootValue: 37.5,
propList: ['*']
})
]
},
preprocessorOptions: {
scss: { api: 'modern-compiler' }
}
},
plugins: [
vue(),
vueJsx(),
AutoImport({ resolvers: [VantResolver()] }),
Components({ resolvers: [VantResolver()] })
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', __vite_injected_original_import_meta_url))
}
}
};
});
export { vite_config_default as default };
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJFOlxcXFx5aWppYW9mdVxcXFx5aWxpXFxcXEdJVFxcXFx5bHN0LXN1cnZleS1oNVwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiRTpcXFxceWlqaWFvZnVcXFxceWlsaVxcXFxHSVRcXFxceWxzdC1zdXJ2ZXktaDVcXFxcdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0U6L3lpamlhb2Z1L3lpbGkvR0lUL3lsc3Qtc3VydmV5LWg1L3ZpdGUuY29uZmlnLnRzXCI7Ly8gdml0ZS5jb25maWcudHNcclxuaW1wb3J0IHsgZGVmaW5lQ29uZmlnLCBsb2FkRW52IH0gZnJvbSAndml0ZSc7IC8vIFx1NEVDRSB2aXRlIFx1NUJGQ1x1NTE2NSBsb2FkRW52XHJcbmltcG9ydCB2dWUgZnJvbSAnQHZpdGVqcy9wbHVnaW4tdnVlJztcclxuaW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnO1xyXG5pbXBvcnQgdnVlSnN4IGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZS1qc3gnO1xyXG5pbXBvcnQgQXV0b0ltcG9ydCBmcm9tICd1bnBsdWdpbi1hdXRvLWltcG9ydC92aXRlJztcclxuaW1wb3J0IENvbXBvbmVudHMgZnJvbSAndW5wbHVnaW4tdnVlLWNvbXBvbmVudHMvdml0ZSc7XHJcbmltcG9ydCB7IFZhbnRSZXNvbHZlciB9IGZyb20gJ3VucGx1Z2luLXZ1ZS1jb21wb25lbnRzL3Jlc29sdmVycyc7XHJcbmltcG9ydCBwb3N0Q3NzUHhUb1JlbSBmcm9tICdwb3N0Y3NzLXB4dG9yZW0nO1xyXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoKHsgbW9kZSB9KSA9PiB7XHJcbiAgLy8gXHU2M0E1XHU2NTM2IG1vZGUgXHU1M0MyXHU2NTcwXHJcbiAgLy8gXHU2QjYzXHU3ODZFXHU1MkEwXHU4RjdEXHU3M0FGXHU1ODgzXHU1M0Q4XHU5MUNGXHJcbiAgY29uc3QgZW52ID0gbG9hZEVudihtb2RlLCBwcm9jZXNzLmN3ZCgpKTtcclxuXHJcbiAgLy8gXHU0RUNFIGVudiBcdTVCRjlcdThDNjFcdTRFMkRcdTgzQjdcdTUzRDZcdTUzRDhcdTkxQ0ZcclxuICBjb25zdCBwcm94eVVybCA9IGVudi5WSVRFX0FQUF9CQVNFVVJMO1xyXG4gIGNvbnN0IHByb3h5VXJsRGVsaXZlcnkgPSBlbnYuVklURV9BUFBfREVMSVZFUllfQkFTRVVSTDtcclxuICByZXR1cm4ge1xyXG4gICAgLy8gXHU1RkM1XHU5ODdCIHJldHVybiBcdTkxNERcdTdGNkVcdTVCRjlcdThDNjFcclxuICAgIHNlcnZlcjoge1xyXG4gICAgICBob3N0OiAnMC4wLjAuMCcsXHJcbiAgICAgIHBvcnQ6IDMwMDAsXHJcbiAgICAgIHByb3h5OiB7XHJcbiAgICAgICAgJy9iYWNrZW5kLWFwaSc6IHtcclxuICAgICAgICAgIHRhcmdldDogcHJveHlVcmwsXHJcbiAgICAgICAgICBjaGFuZ2VPcmlnaW46IHRydWUsXHJcbiAgICAgICAgICBwYXRoUmV3cml0ZToge1xyXG4gICAgICAgICAgICAnXi9iYWNrZW5kLWFwaSc6ICcnIC8vIFx1OERFRlx1NUY4NFx1OTFDRFx1NTE5OVxyXG4gICAgICAgICAgfSxcclxuICAgICAgICAgIC8vIGJ5cGFzczogKHJlcSkgPT4gcmVxLmhlYWRlcnMuYWNjZXB0Py5pbmRleE9mKCdodG1sJykgIT09IC0xLCAvLyBcdThERjNcdThGQzcgSFRNTCBcdThCRjdcdTZDNDJcclxuICAgICAgICAgIGNvb2tpZURvbWFpblJld3JpdGU6ICdsb2NhbGhvc3QnXHJcbiAgICAgICAgfSxcclxuICAgICAgICAnL3JlcXVlc3QtamF2YSc6IHtcclxuICAgICAgICAgIHRhcmdldDogYCR7cHJveHlVcmxEZWxpdmVyeX0vYXBpYCxcclxuICAgICAgICAgIGNoYW5nZU9yaWdpbjogdHJ1ZSxcclxuICAgICAgICAgIHBhdGhSZXdyaXRlOiB7ICdeL3JlcXVlc3QtamF2YSc6ICcnIH1cclxuICAgICAgICB9XHJcbiAgICAgIH1cclxuICAgIH0sXHJcbiAgICBjc3M6IHtcclxuICAgICAgcG9zdGNzczoge1xyXG4gICAgICAgIHBsdWdpbnM6IFtcclxuICAgICAgICAgIHBvc3RDc3NQeFRvUmVtKHtcclxuICAgICAgICAgICAgcm9vdFZhbHVlOiAzNy41LFxyXG4gICAgICAgICAgICBwcm9wTGlzdDogWycqJ11cclxuICAgICAgICAgIH0pXHJcbiAgICAgICAgXVxyXG4gICAgICB9LFxyXG4gICAgICBwcmVwcm9jZXNzb3JPcHRpb25zOiB7XHJcbiAgICAgICAgc2NzczogeyBhcGk6ICdtb2Rlcm4tY29tcGlsZXInIH1cclxuICAgICAgfVxyXG4gICAgfSxcclxuICAgIHBsdWdpbnM6IFtcclxuICAgICAgdnVlKCksXHJcbiAgICAgIHZ1ZUpzeCgpLFxyXG4gICAgICBBdXRvSW1wb3J0KHsgcmVzb2x2ZXJzOiBbVmFudFJlc29sdmVyKCldIH0pLFxyXG4gICAgICBDb21wb25lbnRzKHsgcmVzb2x2ZXJzOiBbVmFudFJlc29sdmVyKCldIH0pXHJcbiAgICBdLFxyXG4gICAgcmVzb2x2ZToge1xyXG4gICAgICBhbGlhczoge1xyXG4gICAgICAgICdAJzogZmlsZVVSTFRvUGF0aChuZXcgVVJMKCcuL3NyYycsIGltcG9ydC5tZXRhLnVybCkpXHJcbiAgICAgIH1cclxuICAgIH1cclxuICB9O1xyXG59KTtcclxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUNBLFNBQVMsY0FBYyxlQUFlO0FBQ3RDLE9BQU8sU0FBUztBQUNoQixTQUFTLGVBQWUsV0FBVztBQUNuQyxPQUFPLFlBQVk7QUFDbkIsT0FBTyxnQkFBZ0I7QUFDdkIsT0FBTyxnQkFBZ0I7QUFDdkIsU0FBUyxvQkFBb0I7QUFDN0IsT0FBTyxvQkFBb0I7QUFSNEosSUFBTSwyQ0FBMkM7QUFTeE8sSUFBTyxzQkFBUSxhQUFhLENBQUMsRUFBRSxLQUFLLE1BQU07QUFHeEMsUUFBTSxNQUFNLFFBQVEsTUFBTSxRQUFRLElBQUksQ0FBQztBQUd2QyxRQUFNLFdBQVcsSUFBSTtBQUNyQixRQUFNLG1CQUFtQixJQUFJO0FBQzdCLFNBQU87QUFBQTtBQUFBLElBRUwsUUFBUTtBQUFBLE1BQ04sTUFBTTtBQUFBLE1BQ04sTUFBTTtBQUFBLE1BQ04sT0FBTztBQUFBLFFBQ0wsZ0JBQWdCO0FBQUEsVUFDZCxRQUFRO0FBQUEsVUFDUixjQUFjO0FBQUEsVUFDZCxhQUFhO0FBQUEsWUFDWCxpQkFBaUI7QUFBQTtBQUFBLFVBQ25CO0FBQUE7QUFBQSxVQUVBLHFCQUFxQjtBQUFBLFFBQ3ZCO0FBQUEsUUFDQSxpQkFBaUI7QUFBQSxVQUNmLFFBQVEsR0FBRyxnQkFBZ0I7QUFBQSxVQUMzQixjQUFjO0FBQUEsVUFDZCxhQUFhLEVBQUUsa0JBQWtCLEdBQUc7QUFBQSxRQUN0QztBQUFBLE1BQ0Y7QUFBQSxJQUNGO0FBQUEsSUFDQSxLQUFLO0FBQUEsTUFDSCxTQUFTO0FBQUEsUUFDUCxTQUFTO0FBQUEsVUFDUCxlQUFlO0FBQUEsWUFDYixXQUFXO0FBQUEsWUFDWCxVQUFVLENBQUMsR0FBRztBQUFBLFVBQ2hCLENBQUM7QUFBQSxRQUNIO0FBQUEsTUFDRjtBQUFBLE1BQ0EscUJBQXFCO0FBQUEsUUFDbkIsTUFBTSxFQUFFLEtBQUssa0JBQWtCO0FBQUEsTUFDakM7QUFBQSxJQUNGO0FBQUEsSUFDQSxTQUFTO0FBQUEsTUFDUCxJQUFJO0FBQUEsTUFDSixPQUFPO0FBQUEsTUFDUCxXQUFXLEVBQUUsV0FBVyxDQUFDLGFBQWEsQ0FBQyxFQUFFLENBQUM7QUFBQSxNQUMxQyxXQUFXLEVBQUUsV0FBVyxDQUFDLGFBQWEsQ0FBQyxFQUFFLENBQUM7QUFBQSxJQUM1QztBQUFBLElBQ0EsU0FBUztBQUFBLE1BQ1AsT0FBTztBQUFBLFFBQ0wsS0FBSyxjQUFjLElBQUksSUFBSSxTQUFTLHdDQUFlLENBQUM7QUFBQSxNQUN0RDtBQUFBLElBQ0Y7QUFBQSxFQUNGO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K