Merge branch 'feature-enterprise-wechat-launch' into uat

# Conflicts:
#	src/views/Home/Index.vue
This commit is contained in:
王博冉
2022-11-15 15:05:52 +08:00
19 changed files with 1463 additions and 22 deletions

View File

@@ -11,3 +11,5 @@ VUE_APP_MESSAGE_CENTER ='http://gtech-gateway.dcin-test.digitalyili.com/apigtech
#VUE_APP_SOCKETURL = 'wss://planetg-java.test.automark.cc/survey_sync'
VUE_APP_SOCKETURL = 'wss://ylst-api-uat.dctest.digitalyili.com/survey_sync'
VUE_APP_JSONPURL = 'https://iam-uat.dctest.digitalyili.com/idp/restful/getIDPToken'
VUE_APP_YQRURL = 'https://ocp-uat-ain.digitalyili.com'

View File

@@ -10,3 +10,6 @@ VUE_APP_MESSAGE_CENTER ='http://gtech-gateway.cxpin.digitalyili.com/apigtech/mes
VUE_APP_LOGIN = 'https://yip.digitalyili.com//login'
VUE_APP_SOCKETURL = 'wss://ylst-api.xapi.digitalyili.com/survey_sync'
VUE_APP_JSONPURL = 'https://iam.digitalyili.com/idp/restful/getIDPToken'
VUE_APP_YQRURL = 'https://ocp.digitalyili.com'

View File

@@ -13,3 +13,6 @@ VUE_APP_LOGIN = 'https://yip-uat.dctest.digitalyili.com/login'
VUE_APP_SOCKETURL = 'wss://ylst-api-uat.dctest.digitalyili.com/survey_sync'
VUE_APP_JSONPURL = 'https://iam-uat.dctest.digitalyili.com/idp/restful/getIDPToken'
VUE_APP_YQRURL = 'https://ocp-uat-ain.digitalyili.com'

View File

@@ -13,3 +13,5 @@ VUE_APP_LOGIN = 'https://yip-uat.dctest.digitalyili.com/login'
VUE_APP_SOCKETURL = 'wss://ylst-api-uat.dctest.digitalyili.com/survey_sync'
VUE_APP_JSONPURL = 'https://iam-uat.dctest.digitalyili.com/idp/restful/getIDPToken'
VUE_APP_YQRURL = 'https://ocp-uat-ain.digitalyili.com'

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -24,7 +24,7 @@
>
<a-form :label-col="{ span: 6 }">
<a-form-item label="地址">
<a-input v-model:value="linkAddress" placeholder="请输入地址" />
<a-input v-model:value="linkAddress" placeholder="请输入地址" :disabled="fixation" />
</a-form-item>
<a-form-item label="显示文字">
<a-input v-model:value="linkText" placeholder="请输入显示的文字" />
@@ -266,6 +266,7 @@ import { useStore } from "vuex";
import { useRoute } from "vue-router";
import { nodeHandle } from "../views/planetDesign/Design/js/util.js";
import { baseOss } from "../config.js";
import * as cheerio from 'cheerio';
export default {
name: "Tinymce",
components: { Editor },
@@ -322,13 +323,29 @@ export default {
type: Boolean,
default: false,
},
isLinkContent: {
type: Boolean,
default: false,
},
// 固定插入地址
isFixation: {
type: Boolean,
default: false,
},
// 最大数量
maxNum: {
type: Number,
default: NaN
}
},
setup(props, context) {
const store = useStore();
const route = useRoute();
const sn = computed(() => route.query.sn || "");
const fixation = ref(props.isFixation)
const { linkAddress, linkText, linkDialogVisible, linkDiglogOkHandle } =
linkDialog();
linkDialog(fixation,store.state.throw.hrefUrl);
const {
d3Address,
d3Width,
@@ -371,13 +388,27 @@ export default {
quesList,
quoteList,
quoteDialogVisible,
isLinkContent: props.isLinkContent,
};
const { content, init, editorId, fileInput } = initTinymce(dataJson);
const { fileChange } = imageHandle(editorId, props.isImageStyle, sn);
const legendFormat = (str)=>{
if(str){
const $ = cheerio.load(str)
const text = $.text().trim()
return text
}
return str
}
onMounted(() => {
tinymce.init(init);
});
watch(content, (val, oldVal) => {
let num = legendFormat(content.value).length
if(num>=props.maxNum) {
message.warning(`最大输入字数为${props.maxNum}`)
return
}
context.emit("update:editorData", content.value);
});
watch(
@@ -413,6 +444,8 @@ export default {
addQuoListHandle,
fileChange,
delQuoListHandle,
store,
fixation
};
},
};
@@ -447,6 +480,7 @@ function initTinymce(data) {
quoteDialogVisible,
isImageStyle,
sn,
isLinkContent,
} = data;
const content = ref("");
const fileInput = ref(null);
@@ -464,7 +498,7 @@ function initTinymce(data) {
plugins: ["autoresize", "paste"],
toolbar_mode: "wrap",
toolbar_persist: true,
toolbar:
toolbar: isLinkContent ? "linkButton" :
"bold italic underline fontsizeselect quoteButton imgButton linkButton moreButton",
fontsize_formats: "12px 14px 16px 18px 20px 22px 24px 26px 28px",
images_upload_handler: (blobInfo, success, failure, progress) => {},
@@ -528,6 +562,23 @@ function initTinymce(data) {
context.emit("more", content.value);
},
});
editor.ui.registry.addIcon("linkIcon", link);
editor.ui.registry.addButton("linkButton", {
icon: "linkIcon",
onAction: function (e) {
const wrapperNode = tinymce.editors[editor.id].selection.getNode();
const content = tinymce.editors[editor.id].selection.getContent();
const contentNode = document.createElement("div");
contentNode.innerHTML = content;
linkText.value = contentNode.innerText;
if (wrapperNode.tagName.toLowerCase() === "a") {
tinymce.editors[editor.id].selection.select(wrapperNode, true);
linkText.value = wrapperNode.innerText;
linkAddress.value = wrapperNode.href || "";
}
linkDialogVisible.value = true;
},
});
commonFunc(editor);
editor.on("init", (e) => {
// 等页面渲染完毕,给工具栏绑定拖拽事件并且全选内容
@@ -671,10 +722,13 @@ function initTinymce(data) {
/**
* 插入链接弹框crud
*/
function linkDialog() {
function linkDialog(fixation,hrefUrl) {
const linkAddress = ref("");
const linkText = ref(""); // 用于显示
const linkDialogVisible = ref(false);
if(fixation.value) {
linkAddress.value = hrefUrl
}
const linkDiglogOkHandle = (id) => {
if (!linkAddress.value || !linkText.value) {
message.info("请输入文字和链接地址");

View File

@@ -6,4 +6,5 @@ module.exports = {
loginUrl: process.env.VUE_APP_LOGIN,
socketUrl: process.env.VUE_APP_SOCKETURL,
jsonpUrl: process.env.VUE_APP_JSONPURL,
jqrUrl: process.env.VUE_APP_YQRURL,
};

View File

@@ -275,6 +275,12 @@ const constantRoutes = [
meta: { showPreview: false, showPublish: false, keepAlive: true, showShare: true },
component: () => import(/* webpackChunkName: "publish" */ '@/views/Publish/launch-center/launch-task/create')
},
{
path: 'enterprise-weChat',
name: 'enterprise-weChat',
meta: { showPreview: false, showPublish: false, keepAlive: true, showShare: true, permission:'super_admin_flag' },
component: () => import(/* webpackChunkName: "publish" */ '@/views/Publish/launch-center/launch-task/enterpriseWeChat')
},
// {
// path: "link",
// name: "link",
@@ -424,6 +430,16 @@ router.beforeEach((to, from, next) => {
// window.parent.location.href = 'https://yip-uat.dctest.digitalyili.com/login';
}
}
if(!to.meta.permission) {
next()
return
}
if(!JSON.parse(localStorage.getItem('plantUserInfo'))[to.meta.permission]) {
next({
path:'/error/404',
})
return
}
next()
})
export default router

View File

@@ -0,0 +1,15 @@
export default {
namespaced: true,
state: {
// 企微投放链接地址
hrefUrl: '',
},
getters: {},
mutations: {},
actions: {
changeHrefUrl ({ state }, url) {
state.hrefUrl = url
},
},
};

View File

@@ -59,6 +59,7 @@ export default defineComponent({
localStorage.setItem("plantUserInfo", JSON.stringify(data));
localStorage.setItem("plantId", JSON.stringify(data.id));
console.log(store.state.common.userInfo)
store.commit("common/M_COMMON_SET_USERINFO", JSON.stringify(data));
} catch (error) {
message.error(
error.data?.message || error.message || "服务器错误"

View File

@@ -69,6 +69,21 @@ export function applications (params) {
})
}
/* 企微投放-人群包列表 */
export function getActLists (params) {
return request({
url: `/console/release_task/actLists`,
method: 'GET',
params
})
}
/* 企微投放-链接 */
export function getPublishUrl (sn,params) {
return request({
url: `/console/deliverys/surveys/${sn}/publish_url`,
method: 'GET',
params,
})
}

View File

@@ -0,0 +1,284 @@
<template>
<div
class="ques-tiny"
:style="{
background: edit ? background : '',
width: width > 0 ? `${width}px` : '',
}"
v-click-outside="onClickOutsides"
@mouseup="clickHandle"
>
<div
v-if="edit"
class="ques-tiny-mce"
:style="{ opacity: opacityChange ? 1 : 0 }"
>
<tinymce
v-if="!visible"
v-model:editorData="content"
:show="edit"
:preventKeyDown="preventKeyDown"
:curtailMinHeight="minHeight"
:placeholder="placeholder"
:isSelectAll="true"
:background="background"
:isImageStyle="isImageStyle"
:isLinkContent="true"
:isFixation="true"
:maxNum="1000"
@keyDown="keyDownHandle"
></tinymce>
</div>
<div
class="flex-align"
:class="{ 'ques-tiny-after': hasAfter }"
:style="{ minHeight: `${minHeight}px` }"
v-show="!edit"
>
<div
v-if="content"
class="ques-tiny-text content-text"
:class="{ 'ques-tiny-text-hover': showHover }"
style="overflow-wrap: break-word; word-break: break-word"
v-html="content"
></div>
<div v-if="defaultStem && !content" class="ques-tiny-text-no content-text">
{{ placeholder }}
</div>
</div>
<div v-if="edit" @click.stop="">
<slot name="content"> </slot>
</div>
<a-modal
v-model:visible="visible"
width="1000"
:destroyOnClose="false"
:centered="true"
:maskClosable="false"
title="富文本编辑"
@ok="sureHandle"
>
<tinymce
v-model:editorData="content"
:openMoreDialog="visible"
:curtail="false"
:isImageStyle="isImageStyle"
></tinymce>
</a-modal>
</div>
</template>
<script>
import { ref } from "@vue/reactivity";
import { watch } from "@vue/runtime-core";
import Tinymce from "../../../../components/Tinymce.vue";
export default {
name: "QuestionTinymceOption",
components: { Tinymce },
props: {
html: {
type: String,
default: "",
},
hasAfter: {
type: [Boolean, Number],
default: false,
},
minHeight: {
type: Number,
default: 30,
},
preventKeyDown: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
default: "请输入",
},
defaultStem: {
type: String,
default: "",
},
showHover: {
type: Boolean,
default: false,
},
background: {
type: String,
default: "#f5f5f5",
},
width: {
type: Number,
default: 0,
},
isImageStyle: {
type: Boolean,
default: false,
},
disabled: {
type: [Boolean, Number],
default: false,
},
},
setup(props, context) {
const content = ref("");
const { edit, opacityChange, clickHandle, keyDownHandle } = editChange(
context,
props
);
const { visible, sureHandle, moreHandle } = modalChange(content, edit);
watch(
() => props.html,
(val) => {
content.value = val;
},
{ immediate: true }
);
watch(edit, (val) => {
context.emit("focusChange", val, edit);
});
watch(content, (val) => {
context.emit("update:html", val);
context.emit("change", val);
});
const onClickOutsides = (e) => {
opacityChange.value = false;
edit.value = false;
};
return {
content,
edit,
opacityChange,
visible,
sureHandle,
clickHandle,
moreHandle,
keyDownHandle,
onClickOutsides,
};
},
};
/**
* 富文本事件处理
*/
function editChange(context, props) {
const edit = ref(false);
const opacityChange = ref(false);
const clickHandle = () => {
if (props.disabled) return;
edit.value = true;
setTimeout(() => {
opacityChange.value = true;
}, 100);
};
// 富文本回车事件
const keyDownHandle = () => {
// opacityChange.value = false;
// edit.value = false;
context.emit("keyDown");
};
return {
edit,
opacityChange,
clickHandle,
keyDownHandle,
};
}
/**
* 更多弹框内容处理
*/
function modalChange(content, edit) {
const visible = ref(false);
// modal确认回调
const sureHandle = () => {
visible.value = false;
edit.value = false;
};
// 富文本更多按钮打开弹框
const moreHandle = (val) => {
visible.value = true;
content.value = val;
};
return {
visible,
sureHandle,
moreHandle,
};
}
</script>
<style lang="scss" scoped>
.ques-tiny {
flex: 1;
display: flex;
align-items: center;
&-after {
&::after {
display: inline-block;
margin-left: 14px;
border: 1px solid #d8d8d8;
border-radius: 2px;
width: 70px;
height: 18px;
background-color: #fff;
vertical-align: sub;
content: "";
}
& .ques-tiny-text {
flex: initial;
}
}
&-mce {
flex: 1;
opacity: 0;
transition: opacity 0.5s;
}
&-active {
background: #f5f5f5;
}
&-text {
cursor: pointer;
word-break: break-all;
padding: 5px 0;
border: 1px solid transparent;
flex: 1;
&-no {
width: 100%;
cursor: pointer;
padding: 5px 0;
border: 1px solid transparent;
&:hover {
border: 1px dashed #8c8c8c;
}
}
&-hover:hover {
border: 1px dashed #8c8c8c;
}
&-flex {
flex: 1;
}
}
.flex-align {
height: 100%;
display: flex;
align-items: center;
flex: 1;
}
&::v-deep p {
padding: 0;
margin: 0;
}
}
.content-text {
width: 100%;
height: 142px;
border: 1px solid #DFE0E3;
padding: 10px;
border-radius: 5px;
}
.content-text:hover {
border: 1px solid #70B936;
cursor: pointer;
}
</style>

View File

@@ -189,6 +189,7 @@ export default {
url: data?.url || file?.name,
name: file?.name,
attachmentId: data?.attachmentId || file?.name,
fileType: file?.type == 'image/jpeg' || file?.type == 'image/jpg' || file?.type == 'image/png' ? 1 : 2
},
];
isUpload.value = false;

View File

@@ -168,7 +168,8 @@ export default {
// }
// return sampleQuotaValue > 0 ? sampleQuotaValue : undefined;
// };
const columns = reactive([
const columns = ref([]);
const routineColumns = ref([
{
name: "systemChineseName",
title: "投放渠道",
@@ -198,8 +199,30 @@ export default {
slots: { title: "numTitle", customRender: "num" },
align: "right",
},
]);
])
const microColumns = ref([
{
title: "投放渠道",
dataIndex: "applicationChineseName",
width: "50%",
key: "applicationChineseName",
},
{
title: "已有样本数量",
dataIndex: "answer_samples",
align: "center",
width: "25%",
slots: { customRender: "answer_samples" },
key: "answer_samples",
},
{
dataIndex: "num",
key: "num",
align: "center",
slots: { title: "numTitle", customRender: "num" },
align: "right",
},
])
onMounted(() => {
// getNum();
// getList();
@@ -229,7 +252,18 @@ export default {
sampleQuotaSum.value = getSum();
};
const showModal = (pid) => {
const showModal = (pid,type) => {
if(type===1) {
columns.value = []
routineColumns.value.map(item=>{
columns.value.push(item)
})
}else{
columns.value = []
microColumns.value.map(item=>{
columns.value.push(item)
})
}
id.value = pid;
visible.value = true;
console.log("showModal", pid);

View File

@@ -0,0 +1,216 @@
<template>
<div class="link-container">
<sub-title>基本信息</sub-title>
<div class="content">
<a-form
ref="formRef"
:model="formState"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-item ref="deliveryName" label="投放名称" name="deliveryName">
<a-input
placeholder="请输入本次投放名称"
v-model:value="formState.deliveryName"
:disabled="!iscreate"
:show-count="true"
:maxlength="50"
class="input"
>
<template #suffix
><span class="show-count"> {{ formState.deliveryName.length }}/50</span>
</template>
</a-input>
</a-form-item>
<a-form-item label="样本数量限制" name="">
<a-switch class="custom-switch" v-model:checked="formState.checked" :disabled="!iscreate" />
<span v-if="!formState.checked">不开启样本数量限制</span>
<div class="limitation" v-else>
<span>&nbsp; 样本数量达到&nbsp; </span>
<a-input-number :disabled="!iscreate" :min="0" :max="100000" v-model:value="formState.sampleQuota" />
<span>&nbsp; 停止收集数据</span>
</div>
</a-form-item>
<a-form-item label="问卷链接" name="">
<div class="copy">
<a-input v-model:value="formState.code" style="width: 60%" disabled class="input"></a-input>
<div class="copy-btn" @click="handleCopy">
<img class="copy-icon" :src="require('@/assets/img/publish/copy_icon.png')" alt="" />
<span>复制</span>
</div>
</div>
</a-form-item>
</a-form>
</div>
</div>
</template>
<script>
import { ref, watch, onMounted } from 'vue'
import SubTitle from "@/views/DataAnalyse/components/SubTitle.vue";
// import common from "@/api/common.js";
import { Modal, message } from "ant-design-vue";
import { useRoute } from "vue-router";
import { deliveryPlan, getPublishUrl } from "../api/php";
import { useStore } from "vuex";
export default {
components: {
SubTitle,
},
props: {
status: {
type: Number,
default: () => 0,
},
isCreate: {
type: Boolean,
default: () => true,
},
basicData: {
type: Object,
default: () => {},
}
},
setup(props, context) {
const store = useStore();
const formRef = ref();
const route = useRoute();
const iscreate = ref(route.query.iscreate === "1");
const sn = route.query.sn
const rules = {
deliveryName: [
{
required: true,
message: "请输入本次投放名称",
trigger: "blur",
},
],
};
const formState = ref({
deliveryName: "",
checked: false,
sampleQuota: 0,
code: '',
});
const getData = async () => {
// formState.value.code = route.query.code
if (!iscreate.value) {
const { data } = await deliveryPlan({
sn,
deliveryPlanId: route.query.id,
});
console.log('data-----',data);
formState.value.deliveryName = data.title
context.emit('getDetail',data)
}
};
const onSubmit = () => {
return formRef.value.validate();
};
const urlCode = ref('')
const onPublishUrl = async() => {
const url = route.query.code
const params = {
code: url.substring(url.lastIndexOf('?sn=') + 4,url.index)
}
const { data } = await getPublishUrl(sn,params);
urlCode.value = data
formState.value.code = url + '&channelUCode=' + data
store.dispatch("throw/changeHrefUrl", formState.value.code);
};
watch(
()=>props.basicData,
(val)=>{
formState.value.checked = val.samples > -1?true:false
formState.value.sampleQuota = val.samples
formState.value.code = route.query.code + '&channelUCode=' + val.urlUniqCode
store.dispatch("throw/changeHrefUrl", formState.value.code);
},
{ deep:true, immediate: true }
)
onMounted(() => {
if(!iscreate.value) getData();
if(iscreate.value) onPublishUrl()
});
const handleCopy = (text) => {
let input = document.createElement("input");
input.value = formState.value.code;
document.body.appendChild(input);
input.select();
document.execCommand("Copy");
document.body.removeChild(input);
message.success("复制成功");
};
return {
formRef,
labelCol: {
span: 3,
style: {
"text-align": "left",
},
},
wrapperCol: { span: 20 },
other: "",
formState,
rules,
iscreate,
handleCopy,
onSubmit,
urlCode,
};
},
};
</script>
<style lang="scss" scoped>
.link-container {
padding: 32px 24px;
background: #fff;
.content {
margin-top: 16px;
.limitation {
display: inline-block;
}
}
.customer {
display: flex;
justify-content: space-between;
margin-bottom: 25px;
}
.copy {
display: flex;
}
.copy-btn {
margin-left: 20px;
display: flex;
align-items: center;
justify-content: center;
width: 76px;
height: 32px;
border-radius: 2px;
box-shadow: 0 2px 0 rgb(0 0 0 / 2%);
cursor: pointer;
background: #f5f5f5;
&:hover {
background: #d9d9d9;
}
}
.copy-icon {
width: 16px;
height: 16px;
}
}
.input {
width: 328px;
height: 32px;
opacity: 1;
border-radius: 5px;
}
.show-count {
color: rgb(191, 191, 191);
}
</style>

View File

@@ -0,0 +1,318 @@
<template>
<div class="link-containers">
<sub-title>消息中心</sub-title>
<div class="content">
<a-form
ref="formRef"
:model="formState"
:label-col="labelCol"
:wrapper-col="wrapperCol"
:rules="rules"
>
<a-form-item label="消息接收者" name="receiverCodeNameList" class="receiver-name">
<!-- <div style="display: flex"> -->
<a-select
v-model:value="formState.receiverCodeNameList"
show-search
placeholder="请搜索并选择人群包"
style="width: 328px"
:options="state.data"
:filter-option="false"
@search="fetchUser"
class="input-border"
v-if="isCreate"
>
<template v-if="state.fetching" #notFoundContent>
<a-spin size="small" />
</template>
</a-select>
<div v-else class="crowd input-border">
<span>{{formState.receiver}}</span>
<DownOutlined />
</div>
<a-button type="text" class="custom-button" @click="onOperate(1)">
<div class="flex-align">
<i class="iconfont">&#xe689;</i>
<span style="margin-left: 6px">新增人群包</span>
</div>
</a-button>
<span style="border-right: 1px solid #D8D8D8;height: 15px;"></span>
<a-button type="text" class="custom-button" @click="onOperate(2)">
<div class="flex-align">
<!-- <i class="iconfont">&#xe689;</i> -->
<i class="iconfont">&#xe6df;</i>
<span style="margin-left: 6px">查看人群包</span>
</div>
</a-button>
<!-- </div> -->
</a-form-item>
<a-form-item label="消息内容" name="description">
<question-tinymce-option
v-if="isCreate"
v-model:html="formState.description"
ref="tinymce"
:preventKeyDown="true"
placeholder="请输入消息内容"
defaultStem="请输入消息内容"
@update:html="updateHandle"
>
</question-tinymce-option>
<div v-else class="news input-border" v-html="formState.descriptionText"></div>
<div style="float:right"><span :style="{color:legendFormat(formState.description).length>1000?'#e32448':''}">{{legendFormat(formState.description).length}}</span> / 1000</div>
</a-form-item>
<a-form-item label="消息展现示例" name="">
<div class="news-example">
<img :src="require('@/assets/img/business_wechat.png')" alt="">
<div class="news-content" v-html="formState.descriptionTextCase">
</div>
</div>
</a-form-item>
</a-form>
</div>
</div>
</template>
<script>
import { ref, watch, reactive, onMounted } from 'vue'
import { debounce } from 'lodash-es';
import { jqrUrl } from "../../../../config";
import { getActLists } from "../api/php"
import { useRoute } from "vue-router";
import SubTitle from "@/views/DataAnalyse/components/SubTitle.vue";
import QuestionTinymceOption from "./QuestionTinymceOption.vue";
import { DownOutlined } from "@ant-design/icons-vue";
import * as cheerio from 'cheerio';
import { useStore } from "vuex";
export default {
components: {
SubTitle,
QuestionTinymceOption,
DownOutlined,
},
props: {
isCreate: {
type: Boolean,
default: () => true,
},
quantityData: {
type: Object,
default: () => {}
}
},
setup(props, context) {
const store = useStore();
const formRef = ref()
const route = useRoute();
const rules = {
receiverCodeNameList: [
{
required: true,
message: "请选择人群包",
trigger: "change",
},
],
description: [
{
required: true,
message: "请输入消息内容",
trigger: "change",
},
],
};
const formState = ref({
receiverCodeNameList: undefined,
description: "",
receiver: "",
descriptionText: "",
descriptionTextCase: "",
});
let lastFetchId = 0;
const state = reactive({
data: [],
value: [],
fetching: false,
});
const fetchUser = debounce(async (value) => {
lastFetchId += 1;
const fetchId = lastFetchId;
state.data = [];
state.fetching = true;
const res = await getActLists({keyword:value})
if (fetchId !== lastFetchId) {
return;
}
const data = res.data.map(user => ({
label: user.name,
value: JSON.stringify([{receiverCode: user.id,receiverName: user.name}]),
}));
state.data = data;
state.fetching = false;
}, 900);
// 查看,新建人群包
let builtUrl = jqrUrl + 'profile_platform/1/segment/list'
// let viewUrl = jqrUrl + '/profile_platform/1/segment/rule?segId=256'
const onOperate = (type) => {
if (type===1) {
window.open(builtUrl)
}else{
window.open(builtUrl)
}
}
const updateHandle = (val) => {
formState.value.description = val
formState.value.descriptionText = val.replace("&amp;","&").replace(store.state.throw.hrefUrl,"javascript:void(0)")
formState.value.descriptionTextCase = formState.value.descriptionText
}
const onSubmit = () => {
return formRef.value.validate();
};
const iscreate = ref(route.query.iscreate === "1");
const legendFormat = (str)=>{
if(str){
const $ = cheerio.load(str)
const text = $.text().trim()
return text
}
return str
}
watch(
()=>props.quantityData,
(val)=>{
formState.value.receiver = val.receiver
formState.value.description = val.description
formState.value.descriptionText = val.description.indexOf('</a>')>0? val.description.replace("&amp;","&").replace(store.state.throw.hrefUrl,"javascript:void(0)"):val.description
formState.value.descriptionTextCase = formState.value.descriptionText?formState.value.descriptionText:"<p>您好!我们诚邀您参加本次问卷调研。点击 <a href='javascript:void(0)'> 安慕希品牌新品调研 </a> 即可参与。感谢您的支持。</p>"
},
{ deep:true, immediate: true }
)
onMounted(() => {
if(iscreate.value) fetchUser(null);
});
return {
formRef,
formState,
labelCol: {
span: 3,
style: {
"text-align": "left",
},
},
wrapperCol: { span: 21 },
state,
fetchUser,
onOperate,
updateHandle,
rules,
onSubmit,
legendFormat,
};
}
}
</script>
<style lang="scss" scoped>
.link-containers {
// padding: 32px 24px;
background: #fff;
.content {
margin-top: 16px;
.receiver-name :deep(.ant-form-item-control .ant-form-item-control-input .ant-form-item-control-input-content) {
display: flex;
align-items: center;
}
.limitation {
display: inline-block;
}
.news {
height: 142px;
background: #F9F9F9;
opacity: 1;
border: 1px solid #DFE0E3;
padding: 4px 11px;
color: rgba(0, 0, 0, 0.2);
cursor: no-drop;
}
}
.customer {
display: flex;
justify-content: space-between;
margin-bottom: 25px;
}
.copy {
display: flex;
}
.copy-btn {
margin-left: 20px;
display: flex;
align-items: center;
justify-content: center;
width: 76px;
height: 32px;
border-radius: 2px;
box-shadow: 0 2px 0 rgb(0 0 0 / 2%);
cursor: pointer;
background: #f5f5f5;
&:hover {
background: #d9d9d9;
}
}
.copy-icon {
width: 16px;
height: 16px;
}
.crowd {
width: 328px;
height: 32px;
background: #F9F9F9;
opacity: 1;
border: 1px solid #DFE0E3;
color: rgba(0,0,0,0.2);
cursor: no-drop;
padding: 4px 11px;
display: flex;
justify-content: space-between;
align-items: center;
}
}
.input {
width: 328px;
height: 32px;
opacity: 1;
border-radius: 5px;
}
:deep(.ques-tiny-text-no) {
color: #bfbfbf !important;
}
.news-example {
width: 720px;
height: 263px;
background: #F9F9F9;
opacity: 1;
padding: 15px 30px;
img {
width: 45px;
height: 37px;
margin-right: 15px;
display: inline-block;
}
.news-content {
position: absolute;
display: inline-block;
width: 600px;
max-height: 233px;
min-height: 76px;
overflow-y: auto;
background: #FFF;
border-radius: 4px 4px 4px 4px;
opacity: 0.7;
padding: 10px;
}
}
.input-border {
border-radius: 5px;
}
</style>

View File

@@ -135,7 +135,6 @@ export default {
});
return;
}
Modal.confirm({
title: "提示",
content: "确定后,该投放任务将无法修改(除样本数量限制),可在投放中心查询业务进度",
@@ -164,6 +163,7 @@ export default {
{
receiverCode: s.systemAdministratorPhone,
receiverName: s.systemAdministrator,
receiverPhone: s.systemAdministratorPhone,
},
]),
};
@@ -252,6 +252,7 @@ export default {
.link-container {
padding: 32px 24px;
background: #fff;
box-shadow: 0px 0px 6px 0px rgb(0 0 0 / 10%);
.title {
font-size: 16px;
font-weight: bold;

View File

@@ -0,0 +1,323 @@
<template>
<div class="launch-center-create">
<basicInformation ref="basicInformationRef" :isCreate="isCreate" :basicData="basicData" @getDetail="getDetail"></basicInformation>
<div class="link-container" style="margin-top: 20px;">
<div class="tableBox">
<quantityLimitTable :isCreate="isCreate" :quantityData="quantityData" ref="quantityLimitTableRef"></quantityLimitTable>
</div>
<div class="customer-btn" style="text-align: right">
<a-button type="primary" style="margin-right: 12px" v-if="isCreate" @click="save" :loading="loading">
确定投放
</a-button>
</div>
</div>
</div>
</template>
<script>
import basicInformation from "../components/enterpriseBasicInformation.vue";
import quantityLimitTable from "../components/enterpriseQuantityLimitTable.vue";
import { useRoute, useRouter } from "vue-router";
import { onMounted, ref, watch } from "@vue/runtime-core";
import { add, channels } from "../api/php";
import { getQrcode } from "@/api/publish";
import { message, Modal } from "ant-design-vue";
export default {
components: {
basicInformation,
quantityLimitTable,
},
setup() {
const route = useRoute();
const isCreate = ref(false);
const basicInformationRef = ref(null);
const datasource = ref([]);
const loading = ref(false);
const quantityLimitTableRef = ref(null);
const id = ref();
const router = useRouter();
isCreate.value = route.query.iscreate === "1" ? true : false;
watch(
() => route.query.iscreate,
(value) => {
isCreate.value = value === "1" ? true : false;
}
);
const save = async () => {
// 基础信息
// let params = {
// deliveryName:'',
// }
let basicData = await basicInformationRef.value.onSubmit();
let quantityData = await quantityLimitTableRef.value.onSubmit();
// if(!basicData.checked) {
// params.sampleQuota = 0
// }else{
// params.sampleQuota = basicData.sampleQuota
// }
// console.log('basicInformationRef',basicInformationRef.value.formState);
if (quantityData.checked&&!quantityData.sampleQuota) {
message.warning('请输入限制的样本数量')
}
// 消息中心提交
// if (quantityLimitTableRef.value) {
// quantityLimitTableRef.value.onSubmit();
// }
console.log('quantityLimitTableRef.value',quantityLimitTableRef.value.formState);
console.log('basicInformationRef.value.formState',basicInformationRef.value.formState);
Modal.confirm({
title: "提示",
content: "确定后,该投放任务将无法修改(除样本数量限制),可在投放中心查询业务进度",
okText: "确认",
cancelText: "取消",
onOk() {
setTimeout(async () => {
if (basicData&&quantityData) {
// const url = await getUrl(route.query.sn);
loading.value = true;
const data = await add({
deliveryName: basicData.deliveryName,
urlCode: basicInformationRef.value.urlCode,
deliverySource: "2",
description: quantityData.description,
deliveryPlanList: [{
channelId: '企业微信',
deliveryStatus: 1,
outerPlanId:route.query.sn,
receiveApplicationCode: 2,
receiveApplicationName: '企业微信',
sampleQuota: basicInformationRef.value.formState.checked?basicInformationRef.value.formState.sampleQuota:-1,
questionnaireUrl: JSON.parse(JSON.stringify(basicInformationRef.value.formState.code)),
receiverCodeNameList: quantityData.receiverCodeNameList,
}],
sn: route.query.sn,
});
if (data) {
loading.value = false;
Modal.success({
title: "提示",
content: "添加成功",
});
router.back();
}else{
loading.value = false;
}
}
});
},
});
};
const getList = async () => {
if (!(id.value && route.query.sn)) {
return;
}
const { data } = await channels({
sn: route.query.sn,
deliveryPlanId: route.query.id,
}).finally(() => {
});
console.log('channels-data',data);
datasource.value = data[0]
quantityData.value.receiver = data[0].system_administrator
basicData.value.samples = data[0].samples
basicData.value.urlUniqCode = data[0].url_uniq_code
};
// const getUrl = async (sn) => {
// const data = await getQrcode(sn);
// return data.data.url;
// };
watch(
() => route.query.id,
(value) => {
id.value = value;
getList();
},
{ immediate:true }
);
// 获取详情
const getDetail = (val) => {
console.log('val详情',val);
quantityData.value.description = val.memo
}
const quantityData = ref({
description: "",
receiver: "",
})
const basicData = ref({
samples: -1,
urlUniqCode: "",
})
onMounted(() => {
getList();
});
return {
basicInformationRef,
quantityLimitTableRef,
datasource,
isCreate,
save,
getDetail,
quantityData,
basicData,
loading
};
},
};
</script>
<style lang="scss" scoped>
.launch-center-create {
// position: relative;
.link-container {
padding: 32px 24px;
background: #fff;
box-shadow: 0px 0px 6px 0px rgb(0 0 0 / 10%);
.title {
font-size: 16px;
font-weight: bold;
}
.content {
margin-top: 16px;
display: flex;
align-items: center;
}
.left {
display: flex;
}
.isEditTable {
::v-deep .ant-table-tbody > tr:hover:not(.ant-table-expanded-row) > td,
.ant-table-row-hover,
.ant-table-row-hover > td .rowClass {
background-color: #f6f7fa !important;
color: #e1e2e5;
a {
color: #e1e2e5;
}
}
.is_copy {
color: #2575fe;
}
.is_onmouse {
color: #e1e2e5;
padding-left: 15px;
position: relative;
&::before {
position: absolute;
top: 5px;
left: 0;
content: "";
width: 10px;
height: 10px;
background-color: #e1e2e5;
border-radius: 50%;
}
}
}
.tableBox {
margin-top: 16px;
.is_active {
color: #30c57c;
padding-left: 15px;
position: relative;
&::before {
position: absolute;
top: 5px;
left: 0;
content: "";
width: 10px;
height: 10px;
background-color: #30c57c;
border-radius: 50%;
}
}
.is_pause {
color: #8c8c8c;
padding-left: 15px;
position: relative;
&::before {
position: absolute;
top: 5px;
left: 0;
content: "";
width: 10px;
height: 10px;
background-color: #8c8c8c;
border-radius: 50%;
}
}
}
.customer {
display: flex;
justify-content: space-between;
margin-bottom: 25px;
}
.right {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
margin-left: 54px;
}
.copy-btn {
display: flex;
align-items: center;
justify-content: center;
width: 76px;
height: 32px;
border-radius: 2px;
box-shadow: 0 2px 0 rgb(0 0 0 / 2%);
cursor: pointer;
background: #f5f5f5;
&:hover {
background: #d9d9d9;
}
}
.copy-icon {
width: 16px;
height: 16px;
}
.share-icon {
width: 16px;
height: 16px;
}
.qrcode {
width: 80px;
height: 80px;
}
.qrcode img {
display: block;
width: 100%;
height: 100%;
}
.download {
margin-top: 12px;
font-size: 14px;
color: #70b936;
}
.download-icon {
width: 16px;
height: 16px;
}
}
.mr-10 {
margin-right: 10px;
}
.fixed {
bottom: 0;
left: 0;
width: 100%;
height: 60px;
background: #fff;
}
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div v-if="status === 1">
<shareLinks></shareLinks>
<shareLinks ref="shareRef"></shareLinks>
<div class="link-container" style="margin-top: 20px">
<div class="customer">
<sub-title>投放任务</sub-title>
@@ -22,6 +22,9 @@
key="plan_id"
rowKey="plan_id"
>
<template #type="record">
{{record.record.type==1?'小程序':'企业微信'}}
</template>
<template #action="record">
<div>
<a @click="lockLaunchTask(record)" class="mr-10">查看</a>
@@ -65,6 +68,45 @@
</div>
</div>
<div v-else></div>
<a-modal
v-model:visible="visible"
wrapClassName="custom-modal"
:width="userInfo.super_admin_flag?'624px':''"
style="height:368px"
:destroyOnClose="true"
:centered="true"
:maskClosable="false"
:footer="null"
@cancel="onCancel"
title="选择投放类型"
>
<div class="delivery-type">
<div class="wechat-type" :class="isSelected===1?'selected':''" @click="onSelected(1)" v-if="userInfo.super_admin_flag">
<div class="title">
<img :src="require('@/assets/img/business_wechat.png')" alt="">
<span>企业微信</span>
</div>
<div class="present">
<p>适用于内部员工调研</p>
<p>可指定多成员多部门多标签参与</p>
</div>
<a-button class="custom-button" style="color: #000;" @click="onNewBuilt(1)">
开始新建
</a-button>
</div>
<div class="wechat-type" :class="isSelected===2?'selected':''" @click="onSelected(2)">
<div class="title">小程序</div>
<div class="present program">
<p>通过消息中心传递至小程序</p>
<p>请先联系小程序运营人员支持标签参与</p>
</div>
<div class="typeBtn"></div>
<a-button class="custom-button" style="color: #000;" @click="onNewBuilt(2)">
开始新建
</a-button>
</div>
</div>
</a-modal>
</div>
</template>
@@ -72,7 +114,7 @@
import launchListEmpty from "../components/launchListEmpty.vue";
import shareLinks from "../components/shareLinks.vue";
import SubTitle from "@/views/DataAnalyse/components/SubTitle.vue";
import { reactive, ref } from "@vue/reactivity";
import { reactive, ref, computed } from "@vue/reactivity";
// import { list } from "../api/api";
import { useRoute, useRouter } from "vue-router";
import createQuantityLimit from "../components/createQuantityLimit.vue";
@@ -83,6 +125,7 @@ import { Modal } from "ant-design-vue";
import Pagination, { getPagination } from "@components/Pagination.vue";
// import { getAnswerSetting } from "@/views/planetDesign/api/api";
import { canPlanetPublish } from "@/Layouts/utils.js";
import { useStore } from "vuex";
export default {
components: {
@@ -99,6 +142,13 @@ export default {
dataIndex: "title",
key: "title",
},
{
title: "投放类型",
dataIndex: "type",
align: "center",
key: "type",
slots: { customRender: "type" },
},
{
title: "创建人",
dataIndex: "creater",
@@ -138,8 +188,25 @@ export default {
// projectname.value = res.data.project_name;
};
const visible = ref(false)
const addLaunchTask = (iscreate = 0, item) => {
console.log("create", item);
if(iscreate===1) {
visible.value = true
}else{
if(item.record.type===2) {
// 企业微信投放入口
router.push({
path: "enterprise-weChat",
query: {
...route.query,
iscreate: iscreate,
id: item?.record?.plan_id,
code:shareRef.value.code,
},
});
}else{
// 小程序投放入口
router.push({
path: "create",
query: {
@@ -148,13 +215,16 @@ export default {
id: item?.record?.plan_id,
},
});
}
}
};
const lockLaunchTask = (item) => {
console.log('item',item);
addLaunchTask(0, item);
};
const open = (item) => {
console.log("open", item);
createQuantityLimitRef.value.showModal(item.record.plan_id);
createQuantityLimitRef.value.showModal(item.record.plan_id,item.record.type);
};
const getList = async () => {
@@ -202,6 +272,43 @@ export default {
}
};
const isSelected = ref(0)
const onSelected = (isSelect) => {
isSelected.value = isSelect
}
const shareRef = ref()
const onNewBuilt = (type) => {
if(type===1) {
// 企业微信投放入口
router.push({
path: "enterprise-weChat",
query: {
...route.query,
iscreate: 1,
code:shareRef.value.code,
},
});
}else{
// 小程序投放入口
router.push({
path: "create",
query: {
...route.query,
iscreate: 1,
},
});
}
}
const onCancel = () => {
isSelected.value = 0
}
const store = useStore();
// 判断超管权限
let userInfo = computed((state) => {
const info = store.state.common.userInfo
return info||{}
});
onMounted(async () => {
await fetchInfo(route.query.sn);
if (status.value === 1) {
@@ -233,6 +340,13 @@ export default {
createQuantityLimitRef,
onPageChange,
onShowSizeChange,
visible,
isSelected,
onSelected,
onCancel,
onNewBuilt,
shareRef,
userInfo,
};
},
};
@@ -397,4 +511,42 @@ export default {
height: 36px;
border-radius: 6px;
}
.delivery-type {
display: flex;
justify-content: space-between;
.wechat-type {
width: 264px;
height: 256px;
background: #FFFFFF;
border-radius: 6px 6px 6px 6px;
opacity: 1;
border: 1px solid #DFE0E3;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.wechat-type:hover {
cursor: pointer;
}
.present {
margin: 24px 0 31px;
text-align: center;
color: #908B8B;
p {
padding: 0;
margin: 0;
}
}
.program {
margin: 25px 0 32px;
}
img {
margin-right: 9px;
}
.selected {
border: 1px solid #70B936;
}
}
</style>