Browse Source

Merge pull request #26 from PBK-B/master

Sync 21-07-27 18:00 previously submitted code…
old
smalljop 4 years ago
committed by GitHub
parent
commit
1521a80773
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 126
      README.md
  2. 3
      src/components/form/InputMap/index.vue
  3. 2
      src/components/generator/config.js
  4. 6
      src/components/parser/Parser.vue
  5. 1
      src/utils/convert.js
  6. 4
      src/utils/expression.js
  7. 2821
      src/views/form/editor/RightPanel.vue
  8. 6
      src/views/form/index.vue
  9. 9
      src/views/form/preview/ProjectForm.vue
  10. 1
      src/views/form/setting/index.vue
  11. 214
      src/views/form/statistics/analysis.vue
  12. 7
      src/views/form/statistics/index.vue
  13. 61
      src/views/form/write/index.vue
  14. 5
      src/views/official/index.vue
  15. 2
      src/views/project/my/index.vue

126
README.md

@ -1,6 +1,117 @@
# Tduck-WEB
<p></p>
<p></p>
<p align="left">
<img alt="logo" src="https://doc.tduckapp.com/img/banner.png" style="margin-bottom: 0px;">
</p>
<h2 align="left">Tduck 填鸭 —— 表单收集器</h2>
### 特别鸣谢 :heart:
#### 感谢 (https://gitee.com/eoner/vue-automation) 前端脚手架
#### 感谢 (https://gitee.com/mrhj/form-generator) 表单生成器
### 平台简介
Tduck, Form collection system
Tduck 填鸭:是基于B/S架构的一款开源的表单在线收集系统,为企业构建自己的信息反馈系统的综合解决方案,助力企业提高反馈收集客户服务效率。
### 应用场景
主要应用与泛零售、电商、金融、调研、资料收集等行业用户,提供多种工具、多渠道、多方式收集有效信息,更好的提升客户服务,增加客户满意度。
### 功能特性
##### 界面美观,全新element-ui支持,使用流畅
- 以一种全新的设计体验,告别繁琐的设计流程
- 通过简单有趣的方式,轻轻松松完成表单设计
- 支持表单样式模板选择,只为显现更精美的表单
##### 三大模块助力企业能力升级:
- 新建表单:自定义可拖拽式表单设计
- 表单设置:支持多种收集方式设置
- 反馈统计:多维度统计收集的反馈数据
## 在线体验
![首页](https://images.gitee.com/uploads/images/2021/0624/103418_43f3b04e_1495174.png "屏幕截图.png")
### 演示环境&详细文档👮‍♀️
- 演示地址:http://demo.tduckapp.com/
- 演示账号/密码:扫码注册登录
- 文档地址:http://demo.tduckapp.com/
- V2更新日志:https://doc.tduckapp.com/log/
- 部署默认账号: test@tduck.com/12345678
- 获取《使用Docker运行Tduck》教程请加入社群获取
- 获取《tduck-纯小白部署教程》请加入社群获取
- 开源不易如果喜欢请给作者 Star 鼓励
### 加入社群
<img alt="logo" src="https://images.gitee.com/uploads/images/2021/0625/000242_95748ea0_1674451.png" style="margin-bottom: 0px;" width="230px">
#### 项目源码
| 后端源码 | 前端源码 |
|--- | --- |
| https://gitee.com/TDuckApp/tduck-platform | https://gitee.com/TDuckApp/tduck-front |
| https://github.com/TDuckCloud/tduck-platform | https://github.com/TDuckCloud/tduck-platform |
感谢:<img alt="logo" src="https://images.gitee.com/uploads/images/2021/0624/105039_de3beb64_1495174.png" style="margin-bottom: 0px;" width="230px">
提供云服务器资源!
### “项目背景”
目前市面上的表单平台虽然功能强大,但是部分业务组件或者额外收取存储等费用,且费用较高,在数据隐私性较强且有特殊表单需求的场景下无法满足业务需求,
目前国内的的开源平台表单问卷平台比较少,tduck表单平台应运而生。让中小公司拥有独立自定义表单问卷,快速赋能业务。
### “新特性”
- 【表单逻辑】支持对单选,多选框,下拉框等组件进行逻辑设置,目前仅支持选中 未选中等逻辑关系
- 【表单分页】支持对问卷问题较多时,进行分页
- 【手写签名】支持手写签名保存上传
- 【位置选择】支持高德地图进行地理位置选择
- 【手机验证】支持对输入手机号进行短信验证 验证通过则能保存
- 【省市联动】支持省市县联动选择输入
- 【图形选择】图片选择组件,支持单选多选
- 【图片展示】图片展示组件,展示二维码引流等
- 【图片轮播】图片轮播组件,图片和文字轮播显示
- 【文字描述】文件描述组件,支持颜色,位置设置
- 【分割线】 内容分割线
- 【联系人】 手机号,邮箱,身份证输入 格式校验
- 【公开反馈结果】 公开反馈结果设置,开启后填写完成将看到他人填写结果
- 【附件导出】填写完成后可对收集的附件进行导出zip下载
- 【统计筛选】可对填写完的结果进行筛选查询 支持模糊等
- 【表单预览】添加预览按钮,实时预览内容,预览二维码
- 【另存为模板】添加另存为模板按钮,保存项目为模板
- 【UI重构】项目整体UI全面重构,更美观,更简洁
- 【验证码】验证逻辑重构,支持验证码开关
- 【微信功能】 微信功能增加开关配置,可配置关闭微信功能
- 【代码优化】对前后端代码进行部分优化,结构调整
![输入图片说明](https://images.gitee.com/uploads/images/2021/0625/000932_eb5728c8_1674451.png "屏幕截图.png")
### “文档支持”
新的文档平台,后续会不断完善文档,有需要完善的内容可以在GItee提issues反馈,收到反馈我们将第一时间调整,当然有想共享文档的也欢迎提Pr。
![输入图片说明](https://images.gitee.com/uploads/images/2021/0625/001013_eb945bce_1674451.png "屏幕截图.png")
![输入图片说明](https://images.gitee.com/uploads/images/2021/0625/001053_f3c320c4_1674451.png "屏幕截图.png")
### “项目规划”
“让每个企业轻松拥有自己的问卷系统”是Tduck团队不变的初衷;v2版本的开发工作目前算是暂时结束,经过团队内部自测推出,同时欢迎大家进行测试,对存在的问题在Gitee提issues,社群内反馈可能无法及时解决,后续可能会遗忘。我们会定期对issues问题修复和做一些优化,竭力维护一个稳定的v2版本。
![输入图片说明](https://images.gitee.com/uploads/images/2021/0625/001803_d0b579e3_1674451.png "屏幕截图.png")
### :heart: 致谢 !
Tduck诞生非常有趣,不经意之间的一个想法,一步步实现至今。在v2开发期间,团队成员非全职开发,都是业余时间进行打码,所以迭代时间较长,期间群中反馈的问题没有及时给予反馈(深感抱歉)。
Tduck永远珍惜每一位用户,重视每一个issues。
感谢对Tduck团队的支持,以及大家对v2期盼与鼓励。
![s8sdOO.png](https://s3.ax1x.com/2021/01/11/s8sdOO.png)
TDuck 前端源码
#### 项目源码
@ -53,14 +164,9 @@ https://www.ydyno.com/archives/1219.html
- views 页面
#### 特别鸣谢
- 感谢 (https://gitee.com/eoner/vue-automation) 前端脚手架
- 感谢 (https://gitee.com/mrhj/form-generator) 表单生成器
### ChangeLog 持续更新中 :sparkles:
增加单选/多选题型统计图 2021-07-02
![输入图片说明](https://images.gitee.com/uploads/images/2021/0702/180333_6991a4bf_1674451.png "屏幕截图.png")

3
src/components/form/InputMap/index.vue

@ -319,4 +319,7 @@ export default {
margin-bottom: 10px;
}
::v-deep .el-dialog__headerbtn {
z-index: 999;
}
</style>

2
src/components/generator/config.js

@ -602,7 +602,7 @@ export const assistComponents = [
typeId: 'DIVIDER',
__config__: {
label: '分割线',
defaultValue: '分割线',
defaultValue: '',
displayType: true,
showLabel: false,
showDefaultValue: false,

6
src/components/parser/Parser.vue

@ -361,8 +361,10 @@ export default {
if (defaultValue instanceof Array) {
defaultValue.forEach(item => {
if (item) {
let {label} = getObject(_.get(cur, tagOptionKey), 'value', item)
labelStr += label + ','
let labelItem = getObject(_.get(cur, tagOptionKey), 'value', item)
if (labelItem) {
labelStr += labelItem.label + ','
}
}
})
formData[cur.__vModel__] = labelStr

1
src/utils/convert.js

@ -136,6 +136,7 @@ const dataParams = {
'CASCADER': {
'options': 'options',
'filterable': 'filterable',
'show-all-levels': 'showAllLevels',
'multiple': 'props.props.multiple'
},
// 单选框组

4
src/utils/expression.js

@ -8,7 +8,9 @@ const expressionOperator = {
if (!v1) {
return false
}
return v1 == v2
// 当type=CHECK_BOX时此处应为包含关系
return Array.isArray(v1) ? v1.includes(+v2) : v1 == v2
},
ne: function(v1, v2) {
if (!v1) {

2821
src/views/form/editor/RightPanel.vue

File diff suppressed because it is too large

6
src/views/form/index.vue

@ -5,15 +5,15 @@
<el-col :span="2">
<i class="el-icon-back" @click="$router.back(-1)" />
</el-col>
<el-col :span="3">
<el-col :span="3" :md="6">
<img class="header-logo" src="@/assets/images/indexLogo.svg" @click="$router.push({path:'/home'})">
</el-col>
<el-col :span="1">
<el-col :span="1" :md="3">
<el-button type="primary" icon="el-icon-view" @click="previewDialogVisible=true">
预览
</el-button>
</el-col>
<el-col :span="2">
<el-col :span="2" :md="3">
<el-button type="success" icon="el-icon-folder-add" @click="saveProjectAsTemplateHandle">
保存为模板
</el-button>

9
src/views/form/preview/ProjectForm.vue

@ -20,7 +20,7 @@
</h4>
<div
v-show="projectTheme.showDescribe"
class="form-name-text" v-html="formConf.description"
class="form-name-text describe-html" v-html="formConf.description"
/>
<el-divider />
<parser v-if="startParser"
@ -291,6 +291,10 @@ export default {
background-repeat: repeat;
background-color: rgba(229, 239, 247, 0.87);
}
.project-form .describe-html img{
max-width: 780px;
margin: 0px;
}
.project-body::-webkit-scrollbar {
width: 0 !important;
@ -329,5 +333,8 @@ export default {
.submit-btn-form-item button {
width: 80%;
}
.project-form .describe-html img{
width: 95vw!important;
}
}
</style>

1
src/views/form/setting/index.vue

@ -80,6 +80,7 @@
<el-col :span="20" class="setting-input">
<el-input
v-model="userProjectSettingData.submitJumpUrl"
placeholder="https://demo.tduckapp.com"
:show-word-limit="true"
:maxlength="50"
@change="saveUserProjectSetting"

214
src/views/form/statistics/analysis.vue

@ -0,0 +1,214 @@
<template>
<div class="analysis">
<div v-if="list.length && list.length !== 0">
<div v-for="(item, index) in list" :key="index">
<div class="content">
<div class="title">
<span style="font-size: 16px">Q{{ item.label }}{{ item.type }}</span>
<div>
<span>图表类型</span>
<el-select v-model="item.chartType" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
<line-chart
class="chart"
:chart-option="getCharData(item)"
:width="'90vw'"
/>
</div>
<el-divider />
</div>
</div>
<data-empty v-else style="padding: 20px" desc="只有单选、多选、下拉框组件可以生成图表" />
</div>
</template>
<script>
import LineChart from '@/components/echarts/LineChart'
export default {
components: {
LineChart
},
data() {
return {
barChartOptionData: {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255,255,255,0.8)', // rgba
color: 'black',
borderWidth: '1',
borderColor: 'rgb(156,209,255)',
textStyle: {
color: 'black'
},
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisTick: {
alignWithLabel: true
}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '数量',
type: 'line',
barWidth: '40%',
data: [10, 52, 200, 334, 390, 330, 220]
}
]
},
list: [],
options: [
{
value: 'pie',
label: '饼图'
},
/* {
value: "ring",
label: "环形图",
}, */
{
value: 'bar',
label: '柱状图'
},
{
value: 'line',
label: '折线图'
}
]
}
},
mounted() {
this.getData()
},
methods: {
getData() {
this.$api
.get('/user/project/report/analysis', {
params: { projectKey: this.$route.query.key }
})
.then(res => {
this.list = res.data
})
},
getCharData(data) {
const config = {
tooltip: {
backgroundColor: 'rgba(255,255,255,0.8)', // rgba
color: 'black',
borderWidth: '1',
borderColor: 'rgb(156,209,255)',
textStyle: {
color: 'black'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
axisTick: {
alignWithLabel: true
}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '数量',
type: 'pie',
barWidth: '40%'
}
]
}
if (['bar', 'line'].includes(data.chartType)) {
config.tooltip.axisPointer = {
type: 'line'
}
config.tooltip.trigger = 'axis'
config.xAxis[0].data = data.fieldName
config.series[0].data = data.data
config.series[0].type = data.chartType
} else {
//
/* if ("ring" === data.chartType) {
config.series[0].radius = ["40%", "70%"];
config.series[0].emphasis = {
label: {
show: true,
fontSize: '25',
fontWeight: 'bold'
}
}
}else{
delete config.series[0].radius
delete config.series[0].label
} */
config.series[0].data = []
Object.keys(data.map).forEach(key => {
config.series[0].data.push({ name: key, value: data.map[key] })
})
// config.series[0].data = data.map
}
return config
}
}
}
</script>
<style lang="scss" scoped>
.analysis {
width: 100%;
// padding: 20px;
.content {
padding: 20px;
width: 100%;
.title {
font-size: 14px;
padding: 0 30px;
height: 54px;
line-height: 54px;
width: 100%;
display: flex;
justify-content: space-between;
.select {
float: right;
}
}
}
}
</style>

7
src/views/form/statistics/index.vue

@ -7,6 +7,9 @@
<el-tab-pane label="统计视图" name="chart">
<chart />
</el-tab-pane>
<el-tab-pane label="数据分析" name="analysis">
<analysis />
</el-tab-pane>
</el-tabs>
</div>
</template>
@ -14,12 +17,14 @@
<script>
import list from './list'
import chart from './chart'
import analysis from './analysis'
export default {
name: 'ProjectStatistics',
components: {
list,
chart
chart,
analysis
},
data() {
return {

61
src/views/form/write/index.vue

@ -174,8 +174,8 @@ export default {
})
},
/**
* 微信分享
*/
* 微信分享
*/
setWxProjectShare(wx) {
let {shareImg, shareTitle, shareDesc} = this.userProjectSetting
wx.updateAppMessageShareData({
@ -243,8 +243,8 @@ export default {
})
},
/**
* 仅在微信打开
*/
* 仅在微信打开
*/
onlyWxOpenHandle() {
let wxUa = navigator.userAgent.toLowerCase()
let isWeixin = wxUa.indexOf('micromessenger') != -1
@ -270,6 +270,12 @@ export default {
'processData': data.labelFormModel
}).then(() => {
this.writeStatus = 2
if (this.userProjectSetting.submitJumpUrl) {
setTimeout(() => {
window.location.replace(this.userProjectSetting.submitJumpUrl)
}, 1000)
}
})
}
}
@ -278,33 +284,36 @@ export default {
<style lang="scss" scoped>
.write-container {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
.title-icon-view {
display: flex;
align-items: center;
align-content: center;
justify-content: center;
flex-direction: column;
height: 100%;
width: 100%;
display: flex;
align-items: center;
align-content: center;
justify-content: center;
flex-direction: column;
height: 100%;
width: 100%;
}
.icon-view {
width: 59px;
height: 59px;
border-radius: 100px;
background-color: #0076ff;
display: flex;
align-content: center;
align-items: center;
justify-content: center;
width: 59px;
height: 59px;
border-radius: 100px;
background-color: #0076ff;
display: flex;
align-content: center;
align-items: center;
justify-content: center;
}
.success-icon {
text-align: center;
color: white;
font-size: 30px;
text-align: center;
color: white;
font-size: 30px;
}
</style>

5
src/views/official/index.vue

@ -643,7 +643,7 @@ export default {
margin-top: 200px;
img {
width: 100%;
width: 80%;
}
}
}
@ -800,6 +800,9 @@ export default {
}
.content-wrapper-company {
margin-top: 80px !important;
img {
width: 100% !important;
}
}
.footer-page {
text-align: center;

2
src/views/project/my/index.vue

@ -280,7 +280,7 @@ export default {
this.dataShowType = type
},
toProjectHandle(key, type) {
this.$router.push({path: '/project/form', query: {key: key, active: type}})
this.$router.push({path: `/project/form/${type}`, query: {key: key, active: type}})
},
deleteProject(key) {
this.$api.post('/user/project/delete', {'key': key}).then(res => {

Loading…
Cancel
Save