文档服务地址:http://47.92.0.57:3000/ 周报索引地址:http://47.92.0.57:3000/s/NruNXRYmV

Commit 7a3ae5ae by Wang Yuhang

实现打包整个任务的功能,写好终止任务前端部分,修改前端审核页面逻辑

parent af20c18a
......@@ -6235,6 +6235,11 @@
"integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
"dev": true
},
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npm.taobao.org/immediate/download/immediate-3.0.6.tgz",
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
},
"import-cwd": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
......@@ -6786,6 +6791,17 @@
"verror": "1.10.0"
}
},
"jszip": {
"version": "3.5.0",
"resolved": "https://registry.npm.taobao.org/jszip/download/jszip-3.5.0.tgz",
"integrity": "sha1-tP0fNoJFNGZY54H+yWdYAkieFfY=",
"requires": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"set-immediate-shim": "~1.0.1"
}
},
"killable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
......@@ -6836,6 +6852,14 @@
"type-check": "~0.3.2"
}
},
"lie": {
"version": "3.3.0",
"resolved": "https://registry.npm.taobao.org/lie/download/lie-3.3.0.tgz",
"integrity": "sha1-3Pgt7lRfRgdNryAMfBxaCOD0D2o=",
"requires": {
"immediate": "~3.0.5"
}
},
"load-json-file": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
......@@ -8021,8 +8045,7 @@
"pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"parallel-transform": {
"version": "1.2.0",
......@@ -11495,6 +11518,11 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"set-immediate-shim": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/set-immediate-shim/download/set-immediate-shim-1.0.1.tgz",
"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
},
"set-value": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
......
......@@ -15,6 +15,7 @@
"default-passive-events": "^2.0.0",
"element-ui": "^2.13.1",
"file-saver": "^2.0.2",
"jszip": "^3.5.0",
"mathjs": "^7.0.2",
"node-sass": "^4.14.1",
"qs": "^6.9.4",
......
import { baseURL } from './env.js'
import { getSlice } from './api/slice.js'
import { getFiles, getExecutors } from './api/file.js'
import axios from './http.js'
import { saveAs } from 'file-saver'
import JSZip from 'jszip'
export function download (url, relationId) {
url = baseURL + url + '/' + relationId
......@@ -8,3 +13,56 @@ export function download (url, relationId) {
a.click()
document.body.removeChild(a)
}
// 打包整个任务
export async function downloadAll (taskId, taskName) {
var pieceList = await getSliceZips(taskId)
var zip = new JSZip()
pieceList.forEach(({sliceId, blobs}, index) => {
let folder = zip.folder(sliceId)
blobs.forEach(item => folder.file(item.fileName + '.zip', item.blob))
})
zip.generateAsync({type: 'blob'}).then(content => saveAs(content, taskName+'.zip'))
}
// 返回列表 [{sliceId, blobs:{fileName, blob}}, ... ]
async function getSliceZips (taskId) {
// 获取分片列表
var res = await getSlice({ taskId })
var promises = res.data.map(async slice => {
let tmp = await getSliceZip(slice.id, slice.type)
return { sliceId: slice.id, blobs: tmp}
})
return Promise.all(promises)
}
// 返回列表 [{fileName, blob}]
async function getSliceZip (pieceId, type) {
// 获取标注id和文件名列表
var res = await getRelations(pieceId)
var promises = res.map(async ({fileName, relationId}) => {
let blob = getFileZip(relationId, type)
return {fileName, blob}
})
return Promise.all(promises)
}
// 返回列表 [{fileName, relationId}]
async function getRelations (pieceId) {
// 获取文件列表
var res = await getFiles({ pieceId })
var promises = res.data.map(async file => {
let tmp = await getExecutors({
pieceId,
fileId: file.id
})
return {fileName: file.name.split('.')[0], relationId: tmp.data[0].relation_id}
})
return Promise.all(promises)
}
// 返回Blob类型的文件压缩包
function getFileZip (relationId, type) {
var url = type === '文本' ? '/files/downloadZip' : (type === '图片' ? '/image/export/zip' : '/layer/export')
return axios.get(url + '/' + relationId, { responseType: 'blob' })
}
import axios from '@/assets/js/http.js'
function getFiles (params) {
return axios.get('/file/getFiles', { params })
}
function getExecutors (params) {
return axios.get('/file/getExecutors', { params })
}
export {
getFiles,
getExecutors
}
import axios from '@/assets/js/http.js'
function getSlice (params) {
return axios.get('/slice/getPieces', { params })
}
export {
getSlice
}
......@@ -6,25 +6,19 @@
<p>发布时间:{{task.date|formatDate}}</p>
<el-divider></el-divider>
<pieces-table :task-id="task.id" showProgress isCheck>
<!-- 仅当executor不为undefined时(即有人标注)显示功能按钮 -->
<template v-slot:option="slotProps">
<el-button type="primary" size="mini" plain
@click="doOption(slotProps.file,slotProps.piece)"
>审核</el-button>
<el-button v-if="slotProps.executor && !slotProps.executor.state" type="primary" size="mini" plain
@click="goto(slotProps.file, slotProps.piece, slotProps.executor)">待审核</el-button>
<el-button v-if="slotProps.executor && slotProps.executor.state" type="success" size="mini" plain
@click="goto(slotProps.file, slotProps.piece)">修改</el-button>
<el-button type="primary" size="mini" plain :disabled="!slotProps.executor || !slotProps.executor.state"
@click="exportFile(slotProps.piece.type, slotProps.executor.relation_id)" >导出</el-button>
</template>
</pieces-table>
</div>
<el-dialog title="已标注列表" :visible.sync="dialogVisible" width="350px" lock-scroll
@open="getExecutors" @opened="loading=false">
<ul v-loading="loading">
<li v-for="item in executors" :key="item.id">
<span>{{item.nickname}}</span>
<el-button v-if="!item.state" type="primary" size="mini" plain @click="goto(item)">待审核</el-button>
<el-button v-else type="success" size="mini" plain @click="goto(item)">修改</el-button>
<el-button type="primary" size="mini" plain
@click="exportFile(item.relation_id)" >导出</el-button>
</li>
</ul>
</el-dialog>
</div>
</template>
......@@ -39,12 +33,6 @@ export default {
data () {
return {
task: null,
// isCheck: '1', 暂时没有用
dialogVisible: false,
executors: [],
file: undefined,
piece: undefined,
loading: false,
operationSign: 1
}
},
......@@ -52,25 +40,8 @@ export default {
this.task = JSON.parse(decodeURIComponent(this.$route.params.task))
},
methods: {
doOption (file, piece) {
// 打开模态框
this.file = file; this.piece = piece
this.dialogVisible = true
},
getExecutors () {
this.loading = true
var self = this, params = {
pieceId: this.piece.id,
fileId: this.file.id
}
this.axios.get('/file/getExecutors', {
params
}).then(res => {
self.executors = res.data
})
},
goto (executor) {
var self = this, {file, piece, operationSign, task} = this
goto (file, piece, executor) {
var self = this, {operationSign, task} = this
var params = {
file,
task,
......@@ -79,7 +50,6 @@ export default {
piece,
operationSign
}
// console.log(params)
function goto (path) {
self.$router.push({ name: path, params })
}
......@@ -92,13 +62,12 @@ export default {
case '图层': goto('layerCheck')
}
},
exportFile (relationId) {
var piece = this.piece
if (piece.type === '文本') {
exportFile (type, relationId) {
if (type === '文本') {
download('/files/downloadZip', relationId)
} else if (piece.type === '图片') {
} else if (type === '图片') {
download('/image/export/zip', relationId)
} else if (piece.type === '图层') {
} else if (type === '图层') {
download('/layer/export', relationId)
}
}
......
......@@ -80,7 +80,7 @@ export default {
download('/layer/export', relationId)
}
},
giveUpTask(){
giveUpTask () {
// alert(1)
// console.log(this.task.id)
this.$confirm('此操作将放弃该任务标注', '提示', {
......@@ -89,25 +89,24 @@ export default {
type: 'warning'
}).then(() => {
this.axios({
methd:'post',
url:'task/giveUpTask',
params:{
userId:this.$store.state.userInfo.userId,
taskId:this.task.id
methd: 'post',
url: 'task/giveUpTask',
params: {
userId: this.$store.state.userInfo.userId,
taskId: this.task.id
}
}).then(res=>{
if(res.code == 1){
}).then(res => {
if (res.code == 1) {
this.$message({
type: 'info',
message: '任务已放弃标注'
});
this.$router.push({ path: "/ongoing" });
}
else{
})
this.$router.push({ path: '/ongoing' })
} else {
this.$message({
type: 'info',
message: '放弃失败'
});
})
}
}
)
......@@ -115,32 +114,31 @@ export default {
this.$message({
type: 'info',
message: '已取消放弃任务'
});
});
})
})
},
finsihTask(){
finsihTask () {
// 判断分片完成度是否都为100%
// console.log("piece")
// console.log(piece)
this.axios({
method:'post',
url:'task/finsihTask',
params:{
userId:this.$store.state.userInfo.userId,
taskId:this.task.id
method: 'post',
url: 'task/finsihTask',
params: {
userId: this.$store.state.userInfo.userId,
taskId: this.task.id
}
}).then(res=>{
if(res.code == 1){
}).then(res => {
if (res.code == 1) {
this.$message({
type:'info',
message:"完成任务"
type: 'info',
message: '完成任务'
})
this.$router.push({ path: "/ongoing" });
}
else{
this.$router.push({ path: '/ongoing' })
} else {
this.$message({
type:'info',
message:"提交任务失败,有未完成标注分片"
type: 'info',
message: '提交任务失败,有未完成标注分片'
})
}
})
......
<<<<<<< HEAD
<template>
<div class="pieces-table-container">
<el-table
......@@ -25,7 +24,7 @@
<div class="file" v-for="item in map[props.row.id]" :key="item.id">
<i class="el-icon-document"></i>
<span class="file-name">{{item.name}}</span>{{item.size}}
<slot name='option' :file='item' :piece='props.row'></slot>
<slot name='option' :file='item' :piece='props.row' :executor='executorMap[item.id]'></slot>
</div>
</div>
</template>
......@@ -37,6 +36,7 @@
<script>
import '@/mock/api.js'
import {getSlice} from '@/assets/js/api/slice.js'
export default {
name: 'pieces-table',
props: {
......@@ -62,7 +62,8 @@ export default {
return {
tableData: [],
multipleSelection: [],
map: {},
map: {}, // 键值对 pieceId: Array<file>
executorMap: {}, // 键值对 fileId: executor
loading: false
}
},
......@@ -80,15 +81,16 @@ export default {
if (this.withExecutor) params.userId = this.userId
var res = await this.axios.get('/slice/getPieces', { params })
this.tableData = res.data
// 拉取文件信息
res.data.forEach(piece => { self.getFiles(piece.id) })
getSlice()
},
methods: {
selectionChange (val) {
this.multipleSelection = val
},
selectable (row, index) {
return true
return row.selectable
},
complete () {
// todo 未登录选择分片
......@@ -118,208 +120,30 @@ export default {
})
}
},
getFiles (pieceId) {
var params = { pieceId }
async getFiles (pieceId) {
var params = { pieceId }, res
if (this.withExecutor) params.executor = this.userId
var self = this
this.axios.get('/file/getFiles', {
params
}).then(res => {
self.$set(self.map, pieceId, res.data.map((item) => {
item.size = self.unitTransform(item.size) // 单位转换
res = await this.axios.get('/file/getFiles', { params })
this.$set(this.map, pieceId, res.data.map((item) => {
item.size = this.unitTransform(item.size) // 单位转换
return item
}))
})
},
unitTransform (n) { // 单位转换
var unit = ['B', 'KB', 'MB', 'GB'], i = 0
while (n >= 1024 && i < 3) {
n /= 1024
i++
}
return n.toFixed(2) + unit[i]
},
tableRowClassName ({row}) {
if (row.type === '图片') {
return 'image-row'
} else if (row.type === '图层') {
return 'coverage-row'
} else {
return 'text-row'
// 如果是审核页面和已完成页面则拉取标注者信息
if (this.$route.name === 'publishTaskDetail' || this.$route.name === 'checkdetail') {
res.data.forEach(file => this.getExecutors(file.id, pieceId))
}
},
percentage (piece) {
getExecutors (fileId, pieceId) {
var self = this
if (piece.fileNum === 0 || piece.totalNum === 0) return 100
return Math.floor(piece.completedNum / (self.isCheck ? piece.totalNum : piece.fileNum) * 100)
}
}
}
</script>
<style>
.el-table .image-row {
background: oldlace;
}
.el-table .coverage-row {
background: #fef0f0;
}
.el-table .text-row {
background: #ecf5ff;
}
</style>
<style lang="scss" scopde>
.file{
position: relative;
height: 40px;
line-height: 40px;
.file-name{
display: inline-block;
min-width: 240px;
}
.el-button{
float: right;
margin-top: 5px;
margin-left: 10px;
}
}
.el-loading-spinner{
transform: scale(0.5);
}
</style>
=======
<template>
<div class="pieces-table-container">
<el-table
ref="multipleTable"
style="width: 100%"
default-expand-all
:data="tableData"
:row-class-name="tableRowClassName"
@selection-change="selectionChange"
>
<el-table-column v-if="showSelection" type="selection" :selectable="selectable" width="55"></el-table-column>
<el-table-column prop="id" label="分片id" width="120"></el-table-column>
<el-table-column prop="type" label="文件类别" width="120"></el-table-column>
<el-table-column prop="fileNum" label="文件数量" width="120"></el-table-column>
<el-table-column prop="template" label="模板/说明" :width="showProgress ? 240 : ''"></el-table-column>
<el-table-column v-if="showProgress" label="进度">
<template slot-scope="props">
<el-progress :percentage="percentage(props.row)" style="width:150px"></el-progress>
</template>
</el-table-column>
<el-table-column type="expand">
<template slot-scope="props">
<div class="fileList" v-loading="!map[props.row.id]">
<div class="file" v-for="item in map[props.row.id]" :key="item.id">
<i class="el-icon-document"></i>
<span class="file-name">{{item.name}}</span>{{item.size}}
<slot name='option' :file='item' :piece='props.row'></slot>
</div>
</div>
</template>
</el-table-column>
</el-table>
<el-button v-if="showSelection" v-loading="loading" type="primary" style="margin-top:20px" @click="complete">完成选择</el-button>
</div>
</template>
<script>
import '@/mock/api.js'
export default {
name: 'pieces-table',
props: {
taskId: Number, // 接收任务的id
isCheck: { // showProgess为false时忽略此属性,为true时,是审核页面,为false标注页面
type: Boolean,
default: false
},
showSelection: {
type: Boolean,
default: false
},
showProgress: {
type: Boolean,
default: false
},
withExecutor: {
type: Boolean,
default: false
}
},
data () {
return {
tableData: [],
multipleSelection: [],
map: {},
loading: false
this.axios.get('/file/getExecutors', {
params: {
pieceId,
fileId
}
},
computed: {
userId () { return this.$store.state.userInfo.userId }
},
/*
没有userId返回所有,有userId返回user所选的,我的任务和已完成任务需要有userId
isCheck表明是审核页发出的请求,多返回totalNum属性
*/
async beforeMount () {
// 拉取分片信息
var params = { taskId: this.taskId }, self = this
if (this.isCheck) params.isCheck = true
if (this.withExecutor) params.userId = this.userId
var res = await this.axios.get('/slice/getPieces', { params })
this.tableData = res.data
// 拉取文件信息
res.data.forEach(piece => { self.getFiles(piece.id) })
},
methods: {
selectionChange (val) {
this.multipleSelection = val
},
selectable (row, index) {
return row.selectable
},
complete () {
// todo 未登录选择分片
if (this.loading) return
if (this.multipleSelection.length === 0) {
// 提示未选择分片
this.$alert('请选择分片', '提示', {
confirmButtonText: '确定',
type: 'warning'
})
} else {
var selected = this.multipleSelection.map((item) => { return item.id })
var userId = this.userId, self = this; this.loading = true
this.axios.post('/slice/selectPieces', {
userId,
selected
}).then((res) => {
self.loading = false
if (res.code === 1) {
self.$message({ message: '选择成功', type: 'success' })
self.$router.push('/ongoing')
} else {
console.log(res)
self.$message.error(res.message)
}
})
}
},
getFiles (pieceId) {
var params = { pieceId }
if (this.withExecutor) params.executor = this.userId
var self = this
this.axios.get('/file/getFiles', {
params
}).then(res => {
self.$set(self.map, pieceId, res.data.map((item) => {
item.size = self.unitTransform(item.size) // 单位转换
return item
}))
self.$set(self.executorMap, fileId, res.data[0])
})
},
unitTransform (n) { // 单位转换
......@@ -340,9 +164,8 @@ export default {
}
},
percentage (piece) {
var self = this
if (piece.fileNum === 0 || piece.totalNum === 0) return 100
return Math.floor(piece.completedNum / (self.isCheck ? piece.totalNum : piece.fileNum) * 100)
return Math.floor(piece.completedNum / piece.fileNum * 100)
}
}
}
......@@ -380,4 +203,3 @@ export default {
transform: scale(0.5);
}
</style>
>>>>>>> deb97ea6d64cbf5fb4d2d09302f70996e638e63b
......@@ -7,9 +7,18 @@
<div>{{item.name}}</div>
<div>发布者:{{item.creator}}&emsp;&emsp;发布时间:{{item.date|formatDate}}&emsp;&emsp;任务状态:{{item.state}}
</div>
<div v-if="item.state == '已发布' ">
<el-button class="task-enter-btn2" type="primary" plain @click="revise(item.id)">修改</el-button>
<el-button class="task-enter-btn1" @click="deleteRow(index, item.id)" type="primary" plain>删除</el-button>
<div v-if="item.state === '已发布' ">
<el-button class="task-enter-btn3" type="primary" plain @click="revise(item.id)">修改</el-button>
<el-button class="task-enter-btn2" @click="deleteRow(index, item.id)" type="primary" plain>删除</el-button>
</div>
<div v-else-if="item.state === '被选中'">
<el-button class="task-enter-btn2" @click="terminate(index, item.id)" type="primary" plain>终止任务</el-button>
</div>
<div v-else-if="item.state === '已结束'">
<el-button class="task-enter-btn2" @click="exportAll(item)" type="primary" plain>导出整个任务</el-button>
</div>
<div>
<el-button class="task-enter-btn1" @click="detail(item)" type="primary" plain>详情</el-button>
</div>
</div>
</div>
......@@ -21,6 +30,7 @@
<script>
import Navigator from './Navigator'
import { downloadAll } from '@/assets/js/Download.js'
export default {
name: 'publishtask',
components: {
......@@ -36,6 +46,9 @@ export default {
}
}
},
computed: {
userId () { return this.$store.state.userInfo.userId }
},
methods: {
deleteRow (index, taskId) {
var self = this
......@@ -63,13 +76,43 @@ export default {
},
revise (taskId) {
this.$router.push({
name:'release',
params:{
ins : 2,
taskId:taskId,
},
name: 'release',
params: {
ins: 2,
taskId: taskId
}
}).catch(data => {
})
},
terminate (index, taskId) {
var self = this, userId = this.userId
this.$confirm('确定要终止吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
self.axios.post('/task/terminateTask', {
userId,
taskId
}).then((res) => {
if (res.code === 1) {
self.tasks.splice(index, 1)
self.$message({ message: '终止成功', type: 'success' })
} else {
console.error(res)
self.$message.error('终止失败' + res.message)
}
})
})
},
detail (task) {
this.$router.push({
name: 'publishTaskDetail',
params: { task: encodeURIComponent(JSON.stringify(task)) }
})
},
exportAll (task) {
downloadAll(task.id, task.name)
}
}
}
......@@ -85,4 +128,7 @@ export default {
.taskhall-container .taskhall-left .tasklist-container .tasklist .task-item .task-enter-btn2{
right: 100px;
}
.taskhall-container .taskhall-left .tasklist-container .tasklist .task-item .task-enter-btn3{
right: 200px;
}
</style>
<template>
<div class="task-container">
<div class="task-panel">
<h2>{{task.name}}</h2>
<p>发布者:{{task.creator}}</p>
<p>发布时间:{{task.date|formatDate}}</p>
<el-divider></el-divider>
<pieces-table :task-id="task.id">
<!-- 仅当executor不为undefined时(即有人标注)显示功能按钮 -->
<template v-slot:option="slotProps">
<el-button type="primary" size="mini" plain :disabled="!slotProps.executor || !slotProps.executor.state"
@click="exportFile(slotProps.piece.type, slotProps.executor.relation_id)" >导出</el-button>
</template>
</pieces-table>
</div>
</div>
</template>
<script>
import PiecesTable from './PiecesTable'
import {download} from '@/assets/js/Download.js'
export default {
name: 'publishTaskDetail',
components: {
PiecesTable
},
data () {
return {
task: null
}
},
beforeMount () {
this.task = JSON.parse(decodeURIComponent(this.$route.params.task))
},
methods: {
exportFile (type, relationId) {
if (type === '文本') {
download('/files/downloadZip', relationId)
} else if (type === '图片') {
download('/image/export/zip', relationId)
} else if (type === '图层') {
download('/layer/export', relationId)
}
}
}
}
</script>
<style lang="scss" scoped>
@import "../assets/scss/config.scss";
@import "../assets/scss/mixin.scss";
.task-container {
@include container;
.task-panel {
@include whiteBoard;
text-align: left;
padding: 30px 20px 30px 20px;
}
ul{
padding:0 20px 0 20px;
max-height: 400px;
overflow-y: auto;
&::-webkit-scrollbar{
width: 5px;
}
&::-webkit-scrollbar-thumb{
background-color: #E4E7ED;
border-radius: 3px;
}
li{
list-style: none;
margin-bottom: 10px;
overflow: hidden;
span{
float: left;
line-height: 28px;
}
.el-button{
float: right;
margin-left: 10px;
}
}
}
}
</style>
......@@ -11,7 +11,7 @@ import VueAxios from 'vue-axios'
import * as math from 'mathjs'
// cnpm i default-passive-events -S
import 'default-passive-events'
console.log('hello', window.vm)
Vue.prototype.$math = math
Vue.prototype.baseURL = axios.defaults.baseURL // 图片标注接口使用
......
......@@ -8,6 +8,7 @@ import completed from '../components/CompletedTask'
import check from '../components/CheckTask'
import release from '../components/ReleaseTask'
import publishtask from '../components/PublishTask'
import publishtaskDetail from '../components/PublishTaskDetail'
import ongoingtask from '../components/ongoingtask'
import ongoingtaskdetail from '../components/OngoingTaskDetail'
import completeddetail from '../components/CompletedTaskDetail'
......@@ -72,6 +73,11 @@ export default new Router({
component: publishtask
},
{
path: '/publishtaskDetail/:task',
name: 'publishTaskDetail',
component: publishtaskDetail
},
{
path: '/ongoing',
name: 'ongoingtask',
component: ongoingtask
......
var Student = /** @class */ (function () {
function Student(firstName, middleInitial, lastName) {
this.firstName = firstName;
this.middleInitial = middleInitial;
this.lastName = lastName;
this.fullName = firstName + " " + middleInitial + " " + lastName;
}
return Student;
}());
function greeter(person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
var user = new Student("Jane", "M", "User");
document.body.textContent = greeter(user);
[]
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment