ProjectCreate.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. <template>
  2. <div class="create-container" v-loading="loading">
  3. <div class="title h1">创建项目</div>
  4. <div class="create-body">
  5. <el-form :model="project" :rules="rules" ref="project" label-width="12%" class="demo-project">
  6. <el-form-item label="项目名称" prop="name">
  7. <el-input size="small" v-model="project.name" placeholder="请输入项目名称"></el-input>
  8. </el-form-item>
  9. <el-form-item label="联系人" prop="contactName">
  10. <el-input size="small" v-model="project.contactName" placeholder="请输入联系人姓名"></el-input>
  11. </el-form-item>
  12. <el-form-item label="手机号" prop="contactPhone">
  13. <el-input size="small" v-model="project.contactPhone" placeholder="请输入联系人电话"></el-input>
  14. </el-form-item>
  15. <el-form-item label="预算" prop="budget">
  16. <el-input size="small" type="number" v-model="project.budget" placeholder="请输入项目预算">
  17. <template slot="append">¥</template>
  18. </el-input>
  19. </el-form-item>
  20. <el-form-item label="计价标准" prop="valuationStandard">
  21. <el-input type="textarea" style="width: 400px" v-model="project.valuationStandard"
  22. placeholder="请输入项目的计价标准(eg:1200/人天)"></el-input>
  23. </el-form-item>
  24. <el-form-item label="需求描述" prop="desc">
  25. <el-input type="textarea" style="width: 400px" v-model="project.desc"
  26. placeholder="请输入对项目的描述"></el-input>
  27. </el-form-item>
  28. <!--<el-form-item label="价格" prop="price">-->
  29. <!--<el-input type="number" v-model="project.price">-->
  30. <!--<template slot="append">¥</template>-->
  31. <!--</el-input>-->
  32. <!--</el-form-item>-->
  33. <el-form-item label="领域类型" prop="field">
  34. <el-radio-group v-model="project.field">
  35. <span v-for="(item,index) in fields" :key="index">
  36. <el-radio :label="item.code">{{ item.name }}&nbsp;&nbsp;&nbsp;&nbsp;</el-radio>
  37. </span>
  38. </el-radio-group>
  39. </el-form-item>
  40. <el-form-item label="应用类型" prop="platform">
  41. <el-radio-group v-model="project.platform">
  42. <span v-for="(item,index) in platforms" :key="index">
  43. <el-radio :label="item.code">{{ item.name }}&nbsp;&nbsp;&nbsp;&nbsp;</el-radio>
  44. </span>
  45. </el-radio-group>
  46. </el-form-item>
  47. <el-form-item label="测试类型" prop="type">
  48. <el-checkbox-group v-model="project.type">
  49. <span v-for="(item,index) in serviceType" :key="index">
  50. <el-checkbox :label="item.code" name="type">{{item.name}}&nbsp;&nbsp;&nbsp;&nbsp;</el-checkbox>
  51. </span>
  52. </el-checkbox-group>
  53. </el-form-item>
  54. <el-form-item label="项目可见性" prop="resource">
  55. <el-tabs
  56. :tab-position="tabPosition"
  57. v-model="project.resource"
  58. style="max-height: 200px;"
  59. >
  60. <el-tab-pane :label="resourceType[0]" name="0">
  61. <el-radio-group v-model="project.institution" prop="institution">
  62. <el-radio :label="item" name="type" v-for="item,index in institutionArray" :key="index">{{item.name}}
  63. </el-radio>
  64. </el-radio-group>
  65. </el-tab-pane>
  66. <el-tab-pane :label="resourceType[1]" name="1">
  67. <provincecity
  68. ref="addFormProvince"
  69. @selectChange="locationChange"
  70. :provinceCode="project.location.provinceCode"
  71. :cityCode="project.location.cityCode"
  72. ></provincecity>
  73. </el-tab-pane>
  74. <el-tab-pane :label="resourceType[2]" name="2"></el-tab-pane>
  75. </el-tabs>
  76. </el-form-item>
  77. <el-form-item label="需求文档" prop="doc">
  78. <el-upload
  79. style="width: 400px"
  80. drag
  81. class="upload-demo"
  82. action=""
  83. :on-remove="handleRemove"
  84. :before-remove="beforeRemove"
  85. :limit="1"
  86. :on-exceed="handleExceed"
  87. :before-upload="beforeFileUpload"
  88. :http-request="uploadRequireDoc"
  89. :file-list="project.doc"
  90. >
  91. <i class="el-icon-upload"></i>
  92. <div class="el-upload__text">
  93. 将文件拖到此处,或
  94. <em>点击上传</em>
  95. </div>
  96. </el-upload>
  97. </el-form-item>
  98. <el-form-item label="安装包" prop="file">
  99. <el-upload
  100. drag
  101. style="width: 400px"
  102. class="upload-demo"
  103. action=""
  104. :on-remove="handleRemove"
  105. :limit="1"
  106. :on-exceed="handleExceed"
  107. :before-upload="beforeApkUpload"
  108. :http-request="uploadApkFile"
  109. :file-list="project.file"
  110. >
  111. <i class="el-icon-upload"></i>
  112. <div class="el-upload__text">
  113. 将文件拖到此处,或
  114. <em>点击上传</em>
  115. </div>
  116. </el-upload>
  117. </el-form-item>
  118. <el-form-item label="项目截止时间" prop="datetime">
  119. <div class="block">
  120. <el-date-picker
  121. size="small"
  122. v-model="project.datetime"
  123. type="datetime"
  124. placeholder="选择截止时间"
  125. align="right"
  126. :picker-options="pickerOptions"
  127. ></el-date-picker>
  128. </div>
  129. </el-form-item>
  130. <el-form-item>
  131. <div class="btn btn-medium btn-info" v-on:click="submitForm('project')">立即申请</div>
  132. <!--<div class="btn btn-medium" @click="resetForm('project')">重置</div>-->
  133. </el-form-item>
  134. </el-form>
  135. </div>
  136. </div>
  137. </template>
  138. <script>
  139. import Http from '@/js/http.js'
  140. import Apis from '@/js/api.js'
  141. import provincecity from '@/components/commons/ProvinceCity'
  142. import provinceCityJSON from '@/constants/provinceCity.json'
  143. import ResourceType from '@/constants/enum/resource-type'
  144. import {notify} from '@/constants/index'
  145. import {
  146. checkFileType,
  147. getAllInstitutions,
  148. getAllFields,
  149. getAllPlatformTypes,
  150. getAllServiceTypes,
  151. getProvinceNameByProvinceCode,
  152. storageGet
  153. } from '@/js/index'
  154. export default {
  155. name: 'ProjectCreate',
  156. components: {
  157. provincecity
  158. },
  159. data () {
  160. var validatePass = (rule, value, callback) => {
  161. var reg = /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/
  162. if (this.contactPhone) {
  163. if (!reg.test(this.contactPhone)) {
  164. callback(new Error('请检查手机号码'))
  165. } else {
  166. callback()
  167. }
  168. }
  169. }
  170. return {
  171. user: {},
  172. loading: false,
  173. tabPosition: 'top',
  174. institutionArray: [],
  175. fields:[{code: '', name: ''}],
  176. platforms:[{code: '', name: ''}],
  177. // platforms: [],
  178. serviceType: [{code: '', name: ''}],
  179. resourceType: ResourceType,
  180. project: {
  181. userId: 0,
  182. name: '',
  183. contactName: '',
  184. contactPhone: '',
  185. type: [],
  186. platform: '',
  187. valuationStandard:'',
  188. field: '',
  189. desc: '',
  190. doc: [],
  191. file: [],
  192. requireDocUrl: '',
  193. fileUrl: '',
  194. resource: '0',
  195. location: {provinceCode: '3200', cityCode: '3201'},
  196. institution: {},
  197. datetime: '',
  198. price: '',
  199. usage: '',
  200. budget: ''
  201. },
  202. pickerOptions: {
  203. disabledDate(time) {
  204. return time.getTime() <= Date.now();
  205. },
  206. shortcuts: [
  207. {
  208. text: '今天',
  209. onClick (picker) {
  210. picker.$emit('pick', new Date())
  211. }
  212. },
  213. {
  214. text: '明天',
  215. onClick (picker) {
  216. const date = new Date()
  217. date.setTime(date.getTime() + 3600 * 1000 * 24)
  218. picker.$emit('pick', date)
  219. }
  220. },
  221. {
  222. text: '一周后',
  223. onClick (picker) {
  224. const date = new Date()
  225. date.setTime(date.getTime() + 3600 * 1000 * 24 * 7)
  226. picker.$emit('pick', date)
  227. }
  228. }
  229. ]
  230. },
  231. rules: {
  232. name: [
  233. {required: true, message: '请输入项目名称', trigger: 'blur'},
  234. {min: 5, max: 50, message: '项目名称长度在 5 到 50 个字符', trigger: 'blur'}
  235. ],
  236. contactName: [
  237. {required: true, message: '请输入联系人姓名', trigger: 'blur'}
  238. // { min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" }
  239. ],
  240. contactPhone: [
  241. {required: true, message: '请输入手机号', trigger: 'blur'},
  242. {min: 11, max: 11, message: '请输入正确的手机号', trigger: 'blur'},
  243. {
  244. validator: (rule, value, callback) => {
  245. if (!this.checkPhoneNumber(value)) {
  246. callback(new Error('手机号输入有误'))
  247. } else {
  248. callback()
  249. }
  250. }, trigger: 'blur'
  251. },
  252. ],
  253. type: [
  254. {
  255. required: true,
  256. message: '请至少选择一种测试类型',
  257. trigger: 'change'
  258. }
  259. ],
  260. platform: [
  261. {
  262. required: true,
  263. message: '请至少选择一个应用类型',
  264. trigger: 'change'
  265. }
  266. ],
  267. field: [
  268. {
  269. required: true,
  270. message: '请至少选择一个领域类型',
  271. trigger: 'change'
  272. }
  273. ],
  274. desc: [{required: false, message: '请填写描述', trigger: 'blur'}],
  275. //price: [{required: true, message: '请填写价格', trigger: 'blur'}],
  276. budget: [
  277. {required: true, message: '预算不可为空', trigger: 'blur'},
  278. {
  279. validator: (rule, value, callback) => {
  280. if(value < 0){
  281. callback(new Error('请输入不小于0的数'))
  282. } else {
  283. callback()
  284. }
  285. }, trigger: 'blur'
  286. },
  287. ],
  288. resource: [
  289. {required: true},
  290. {
  291. validator: (rule, value, callback) => {
  292. if (value == 0 && this.project.institution.id == null) {
  293. callback(new Error('定向发布至少要选择一个区域管理员'))
  294. } else {
  295. callback()
  296. }
  297. }, trigger: 'change'
  298. },
  299. ],
  300. datetime:[{required: true, message: '截止时间不可为空', trigger: 'blur'}],
  301. }
  302. }
  303. },
  304. mounted () {
  305. this.$nextTick(() => {
  306. this.init()
  307. })
  308. },
  309. watch: {
  310. serviceType (val) {
  311. this.serviceType = val
  312. },
  313. institutionArray (val) {
  314. this.institutionArray = val
  315. },
  316. // 'project.institution' () {
  317. // if (this.project.institution) {
  318. // this.$refs.addFormProvince.resetProviceCity()
  319. // this.project.location = {provinceCode: '', cityCode: ''}
  320. // }
  321. // },
  322. // 'project.location' () {
  323. // if (this.project.location.provinceCode || this.project.location.cityCode) {
  324. // this.project.institution = ''
  325. // }
  326. // },
  327. // 'project.resource' () {
  328. // if (this.project.resource == '2') {
  329. // this.$refs.addFormProvince.resetProviceCity()
  330. // this.project.institution = ''
  331. // this.project.location = {provinceCode: '', cityCode: ''}
  332. // }
  333. // },
  334. deep: true
  335. },
  336. methods: {
  337. updateLocation (location) {
  338. var provinceName = ''
  339. var cityName = ''
  340. for (var item of provinceCityJSON.provinces) {
  341. if (item.code === location.provinceCode) {
  342. provinceName = item.name
  343. for (var city of item.cities) {
  344. if (city.code === location.cityCode) {
  345. cityName = city.name
  346. break
  347. }
  348. }
  349. }
  350. }
  351. return provinceName + ' / ' + cityName
  352. },
  353. locationChange (provinceId, cityId) {
  354. if (provinceId || cityId) {
  355. this.project.location = {provinceCode: provinceId, cityCode: cityId}
  356. }
  357. },
  358. init () {
  359. this.setServiceType()
  360. this.setFields()
  361. this.setPlatforms()
  362. this.setInstitution()
  363. this.setUserInfo()
  364. // this.project.platform.map(item => {
  365. // this.platformType.push(PlatformType[item])
  366. // })
  367. },
  368. submitForm () {
  369. this.$refs['project'].validate(valid => {
  370. if (valid) {
  371. //console.log(this.project)
  372. this.showLoading()
  373. const newLocation = getProvinceNameByProvinceCode(this.project.location.provinceCode, this.project.location.cityCode)
  374. const newProject = {
  375. userId: this.user.userVO.id,
  376. name: this.project.name,
  377. type: this.project.type,
  378. platform: this.project.platform,
  379. field: this.project.field,
  380. desc: this.project.desc,
  381. valuationStandard: this.project.valuationStandard,
  382. resource: this.project.resource,
  383. location: newLocation,
  384. institution: this.project.institution.id,
  385. contactName: this.project.contactName,
  386. contactPhone: this.project.contactPhone,
  387. doc: this.project.requireDocUrl,
  388. file: this.project.fileUrl,
  389. budget: this.project.budget,
  390. datetime: this.project.datetime,
  391. usage: this.project.usage,
  392. price: this.project.price
  393. }
  394. console.log(newProject)
  395. Http.post(Apis.PROJECT.CREATE_PROJECT, newProject).then((res) => {
  396. //notify('success', '创建成功')
  397. this.hideLoading()
  398. this.createProjectSuccess(res.projectDetails.id)
  399. // if (window.history.length <= 1) {
  400. // this.$router.push({path: '/'})
  401. // return false
  402. // } else {
  403. // this.$router.go(-1)
  404. // }
  405. }).catch(error => {
  406. //console.log(error)
  407. this.hideLoading()
  408. notify('error', error.data)
  409. })
  410. } else {
  411. console.log(valid)
  412. this.hideLoading()
  413. notify('error','表单填写错误!')
  414. return false
  415. }
  416. })
  417. },
  418. resetForm (formName) {
  419. this.$refs[formName].resetFields()
  420. this.project.name = ''
  421. this.project.type = []
  422. this.project.platform = ''
  423. this.project.field = ''
  424. this.project.desc = ''
  425. this.project.valuationStandard = ''
  426. this.project.file = ''
  427. this.project.doc = ''
  428. this.project.contactName = ''
  429. this.project.contactPhone = ''
  430. this.project.resource = '非定向'
  431. this.project.institution = ''
  432. this.project.datetime = ''
  433. this.project.price = ''
  434. this.project.usage = ''
  435. this.project.budget = ''
  436. },
  437. beforeApkUpload (file) {
  438. return true;
  439. //const fileType = ['exe', 'apk', 'dmg']
  440. //return checkFileType(file, fileType, this.beforeApkUploadError)
  441. },
  442. beforeApkUploadError () {
  443. this.$message.error('上传文件只能是exe,dmg,apk格式!')
  444. },
  445. beforeFileUpload (file) {
  446. return true;
  447. //const fileTypeList = ['pdf', 'xls', 'xlsx', 'doc', 'docx', 'txt']
  448. //return checkFileType(file, fileTypeList, this.beforeFileUploadError)
  449. },
  450. beforeFileUploadError () {
  451. this.$message.error('上传文件只能是 PDF 、 DOC 、DOCX 、XLS、TXT、XLSX 格式!')
  452. },
  453. loadData () {
  454. Http.get(Apis.PAGE.PROJECT_DETAIL_PAGE).then((res) => {
  455. this.project = res.project
  456. })
  457. },
  458. handleRemove (file, fileList) {
  459. //console.log(file, fileList)
  460. },
  461. handleExceed (files, fileList) {
  462. this.$message.warning(
  463. `当前限制选择 1 个文件,本次选择了 ${
  464. files.length
  465. } 个文件,共选择了 ${files.length + fileList.length} 个文件`
  466. )
  467. },
  468. beforeRemove (file, fileList) {
  469. //return this.$confirm(`确定移除 ${file.name}?`)
  470. },
  471. uploadRequireDoc (param) {
  472. this.showLoading()
  473. const formData = new FormData()
  474. let config = {
  475. //添加请求头
  476. headers: {'Content-Type': 'multipart/form-data'},
  477. }
  478. formData.append('file', param.file)
  479. Http.upload(Apis.FILE.REQUIREMENT_FILE.replace('{userId}', this.user.userVO.id), formData, config).then((res) => {
  480. //console.log('上传成功')
  481. this.hideLoading()
  482. notify('success', '需求文档上传成功')
  483. this.project.requireDocUrl = res.data
  484. //console.log(this.project.doc)
  485. //console.log(res.data)
  486. }).catch((error) => {
  487. this.hideLoading()
  488. this.project.doc = []
  489. notify('error', '需求文档上传失败:' + error.data)
  490. })
  491. },
  492. uploadApkFile (param) {
  493. this.showLoading()
  494. const formData = new FormData()
  495. let config = {
  496. //添加请求头
  497. headers: {'Content-Type': 'multipart/form-data'},
  498. }
  499. formData.append('file', param.file)
  500. Http.upload(Apis.FILE.APK.replace('{userId}', this.user.userVO.id), formData, config).then((res) => {
  501. this.hideLoading()
  502. notify('success', '文件上传成功')
  503. this.project.fileUrl = res.data
  504. console.log(res)
  505. }).catch((error) => {
  506. this.hideLoading()
  507. this.project.file = []
  508. notify('error', '文件上传失败:' + error.data)
  509. })
  510. },
  511. setServiceType () {
  512. getAllServiceTypes().then((res) => {
  513. this.serviceType = res
  514. })
  515. },
  516. setFields(){
  517. getAllFields().then((res) => {
  518. this.fields = res
  519. })
  520. },
  521. setPlatforms () {
  522. getAllPlatformTypes().then((res) => {
  523. this.platforms = res
  524. })
  525. },
  526. setInstitution () {
  527. getAllInstitutions().then((res) => {
  528. this.institutionArray = res
  529. })
  530. },
  531. setUserInfo () {
  532. this.user = storageGet('user')
  533. },
  534. createProjectSuccess (projectId) {
  535. this.$alert('项目创建成功', '创建成功', {
  536. confirmButtonText: '确定',
  537. callback: action => {
  538. this.$router.push({
  539. name: 'Project',
  540. params: {projectId: projectId}
  541. })
  542. }
  543. })
  544. },
  545. checkPhoneNumber(phoneNumber){
  546. return /^1[3456789]\d{9}$/.test(phoneNumber)
  547. },
  548. showLoading () {
  549. this.loading = true
  550. },
  551. hideLoading () {
  552. this.loading = false
  553. }
  554. }
  555. }
  556. </script>
  557. <style lang="less" scoped>
  558. .el-col {
  559. padding: 0 !important;
  560. }
  561. .el-row {
  562. margin-bottom: 10px;
  563. }
  564. .el-radio {
  565. margin: 10px 20px 10px 0;
  566. }
  567. .el-form-item /deep/ .el-tabs__content {
  568. max-height: 120px !important;
  569. overflow: auto;
  570. }
  571. .el-input {
  572. width: 400px;
  573. }
  574. </style>
  575. <!--<el-form-item label="联系方式" prop="contact">-->
  576. <!--<div>-->
  577. <!--<el-row :gutter="2">-->
  578. <!--<el-col :span="2">-->
  579. <!--<span>联系人</span>-->
  580. <!--</el-col>-->
  581. <!--<el-col :span="10">-->
  582. <!--<el-input v-model="project.contactName" placeholder="请输入联系人姓名"></el-input>-->
  583. <!--</el-col>-->
  584. <!--</el-row>-->
  585. <!--<el-row :gutter="2">-->
  586. <!--<el-col :span="2">-->
  587. <!--<span>联系人电话</span>-->
  588. <!--</el-col>-->
  589. <!--<el-col :span="10">-->
  590. <!--<el-input v-model="project.contactPhone" placeholder="请输入联系人电话"></el-input>-->
  591. <!--</el-col>-->
  592. <!--</el-row>-->
  593. <!--</div>-->
  594. <!--</el-form-item>-->
  595. <!--<el-form-item label="用途" prop="usage">-->
  596. <!--<el-input v-model="project.usage"></el-input>-->
  597. <!--</el-form-item>-->