|
@@ -1,6 +1,6 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <!-- 数据目录 -->
|
|
|
|
|
- <div class="pa-3" style="background-color: #FFF;">
|
|
|
|
|
|
|
+ <!-- 数据服务 -->
|
|
|
|
|
+ <div class="pa-3 white">
|
|
|
<m-filter :option="filter" @search="handleSearch" />
|
|
<m-filter :option="filter" @search="handleSearch" />
|
|
|
<table-list
|
|
<table-list
|
|
|
class="mt-3"
|
|
class="mt-3"
|
|
@@ -9,38 +9,117 @@
|
|
|
:items="items"
|
|
:items="items"
|
|
|
:total="total"
|
|
:total="total"
|
|
|
:page-info="pageInfo"
|
|
:page-info="pageInfo"
|
|
|
- :is-tools="false"
|
|
|
|
|
|
|
+ :is-tools="true"
|
|
|
|
|
+ :disable-sort="true"
|
|
|
|
|
+ :can-delete="false"
|
|
|
:show-select="false"
|
|
:show-select="false"
|
|
|
|
|
+ @add="handleAdd"
|
|
|
@pageHandleChange="pageHandleChange"
|
|
@pageHandleChange="pageHandleChange"
|
|
|
- @sort="handleSort"
|
|
|
|
|
>
|
|
>
|
|
|
|
|
+ <template #product_name="{ item }">
|
|
|
|
|
+ <span @click="handlePreview(item)">{{ item.product_name }}</span>
|
|
|
|
|
+ <v-chip v-if="item.has_new_data" color="error" small class="ml-2">新</v-chip>
|
|
|
|
|
+ </template>
|
|
|
<template #status="{ item }">
|
|
<template #status="{ item }">
|
|
|
- <v-chip
|
|
|
|
|
- :color="item.status ? 'success' : 'error'"
|
|
|
|
|
- small
|
|
|
|
|
- >
|
|
|
|
|
- {{ item.status ? '已启用' : '已禁用'}}
|
|
|
|
|
- </v-chip>
|
|
|
|
|
|
|
+ <v-chip :color="getStatusColor(item.status)" small>{{ getStatusText(item.status) }}</v-chip>
|
|
|
</template>
|
|
</template>
|
|
|
- <template #tag="{ item }">
|
|
|
|
|
- {{ item.tag.map(e => e.name_zh).join(',') }}
|
|
|
|
|
|
|
+ <template #last_updated_at="{ item }">
|
|
|
|
|
+ <span>{{ formatDateTime(item.last_updated_at) }}</span>
|
|
|
</template>
|
|
</template>
|
|
|
- <template v-slot:name_zh = "{item}">
|
|
|
|
|
- <span class="defaultLink" @click="toDetail(item)">{{ item.name_zh }}</span>
|
|
|
|
|
|
|
+ <template #actions="{ item }">
|
|
|
|
|
+ <v-btn text color="success" @click="handleAdd(item)">编辑</v-btn>
|
|
|
|
|
+ <v-btn text color="primary" @click="handlePreview(item)">预览</v-btn>
|
|
|
|
|
+ <v-btn text color="info" @click="handleRefresh(item)">刷新</v-btn>
|
|
|
|
|
+ <v-btn text color="error" @click="handleDelete(item)">删除</v-btn>
|
|
|
</template>
|
|
</template>
|
|
|
</table-list>
|
|
</table-list>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 预览对话框 -->
|
|
|
|
|
+ <m-dialog :visible.sync="previewDialog.show" title="数据预览" :footer="false" max-width="90%">
|
|
|
|
|
+ <div v-if="previewDialog.data.product" class="mb-4">
|
|
|
|
|
+ <v-card outlined>
|
|
|
|
|
+ <v-card-text>
|
|
|
|
|
+ <div class="d-flex align-center mb-2">
|
|
|
|
|
+ <span class="text-h6 mr-3">{{ previewDialog.data.product.product_name }}</span>
|
|
|
|
|
+ <v-chip small>{{ previewDialog.data.total_count }} 条记录</v-chip>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="text-body-2">
|
|
|
|
|
+ <span class="mr-4">目标表: {{ previewDialog.data.product.target_schema }}.{{ previewDialog.data.product.target_table }}</span>
|
|
|
|
|
+ <span>列数: {{ previewDialog.data.product.column_count }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </v-card-text>
|
|
|
|
|
+ </v-card>
|
|
|
|
|
+ <v-alert v-if="previewDialog.data.error" type="warning" dense class="ma-3">{{ previewDialog.data.error }}</v-alert>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-loading="previewDialog.loading" style="max-height: 600px; overflow-y: auto;">
|
|
|
|
|
+ <v-simple-table v-if="previewDialog.data.columns && previewDialog.data.columns.length" fixed-header dense>
|
|
|
|
|
+ <template v-slot:default>
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th v-for="col in previewDialog.data.columns" :key="col.name" class="text-left">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div>{{ col.name }}</div>
|
|
|
|
|
+ <small style="color: #909399; font-weight: normal;">{{ col.type }}</small>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ <tr v-for="(row, index) in previewDialog.data.data" :key="index">
|
|
|
|
|
+ <td v-for="col in previewDialog.data.columns" :key="col.name">
|
|
|
|
|
+ {{ row[col.name] }}
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </v-simple-table>
|
|
|
|
|
+ <div v-else class="text-center pa-10 text--secondary">
|
|
|
|
|
+ 暂无数据
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="text-end text--secondary">
|
|
|
|
|
+ <span>已加载 {{ previewDialog.data.preview_count || 0 }} / {{ previewDialog.data.total_count || 0 }} 条</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <v-divider></v-divider>
|
|
|
|
|
+ <v-card-actions>
|
|
|
|
|
+ <v-spacer></v-spacer>
|
|
|
|
|
+ <v-btn
|
|
|
|
|
+ v-if="previewDialog.data.preview_count < previewDialog.data.total_count && previewDialog.limit < 1000"
|
|
|
|
|
+ text
|
|
|
|
|
+ color="primary"
|
|
|
|
|
+ @click="handleLoadMore"
|
|
|
|
|
+ >
|
|
|
|
|
+ 加载更多
|
|
|
|
|
+ </v-btn>
|
|
|
|
|
+ <v-btn v-if="previewDialog.data.product" text color="success" @click="handleDownloadFromPreview">下载Excel</v-btn>
|
|
|
|
|
+ <v-btn text @click="previewDialog.show = false">关闭</v-btn>
|
|
|
|
|
+ </v-card-actions>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </m-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 注册对话框 -->
|
|
|
|
|
+ <m-dialog :visible.sync="registerDialog.show" :title="registerDialog.isEdit ? '编辑数据产品' : '新增数据产品'" @submit="handleRegisterSubmit">
|
|
|
|
|
+ <register-form v-if="registerDialog.show" ref="registerForm" :item-data="registerDialog.form"></register-form>
|
|
|
|
|
+ </m-dialog>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
import MFilter from '@/components/Filter'
|
|
import MFilter from '@/components/Filter'
|
|
|
import TableList from '@/components/List/table'
|
|
import TableList from '@/components/List/table'
|
|
|
-import { api } from '@/api/dataGovernance'
|
|
|
|
|
|
|
+import MDialog from '@/components/Dialog'
|
|
|
|
|
+import RegisterForm from './components/RegisterForm'
|
|
|
|
|
+import { api } from '@/api/dataService'
|
|
|
|
|
+import { formatDate } from '@/utils/date'
|
|
|
|
|
+
|
|
|
export default {
|
|
export default {
|
|
|
- name: 'dataCatalog',
|
|
|
|
|
|
|
+ name: 'dataService',
|
|
|
components: {
|
|
components: {
|
|
|
MFilter,
|
|
MFilter,
|
|
|
- TableList
|
|
|
|
|
|
|
+ TableList,
|
|
|
|
|
+ MDialog,
|
|
|
|
|
+ RegisterForm
|
|
|
},
|
|
},
|
|
|
data () {
|
|
data () {
|
|
|
return {
|
|
return {
|
|
@@ -48,49 +127,91 @@ export default {
|
|
|
loading: false,
|
|
loading: false,
|
|
|
filter: {
|
|
filter: {
|
|
|
list: [
|
|
list: [
|
|
|
- { type: 'textField', value: '', label: '关键词', key: 'title' }
|
|
|
|
|
|
|
+ { type: 'textField', value: '', label: '关键词', key: 'search', placeholder: '搜索产品名称、英文名、描述、表名' },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'select',
|
|
|
|
|
+ value: null,
|
|
|
|
|
+ label: '状态',
|
|
|
|
|
+ key: 'status',
|
|
|
|
|
+ items: [
|
|
|
|
|
+ { label: '全部', value: null },
|
|
|
|
|
+ { label: '活跃', value: 'active' },
|
|
|
|
|
+ { label: '非活跃', value: 'inactive' },
|
|
|
|
|
+ { label: '错误', value: 'error' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ itemText: 'label',
|
|
|
|
|
+ itemValue: 'value'
|
|
|
|
|
+ }
|
|
|
]
|
|
]
|
|
|
},
|
|
},
|
|
|
headers: [
|
|
headers: [
|
|
|
- { text: '中文名', value: 'name_zh' },
|
|
|
|
|
- { text: '英文名', value: 'name_en' },
|
|
|
|
|
- { text: '分类', value: 'category', sortable: false },
|
|
|
|
|
- { text: '描述', value: 'describe', sortable: false },
|
|
|
|
|
- { text: '标签', value: 'tag', sortable: false },
|
|
|
|
|
- { text: '状态', value: 'status', sortable: false },
|
|
|
|
|
- { text: '血缘关系数量', value: 'blood_count', align: 'center' },
|
|
|
|
|
- { text: '创建时间', value: 'create_time' }
|
|
|
|
|
|
|
+ { text: '产品名称', value: 'product_name' },
|
|
|
|
|
+ { text: '英文名称', value: 'product_name_en' },
|
|
|
|
|
+ { text: '目标表', value: 'target_table' },
|
|
|
|
|
+ { text: 'Schema', value: 'target_schema' },
|
|
|
|
|
+ { text: '记录数', value: 'record_count', align: 'center' },
|
|
|
|
|
+ { text: '列数', value: 'column_count', align: 'center' },
|
|
|
|
|
+ { text: '数据流', value: 'source_dataflow_name' },
|
|
|
|
|
+ { text: '状态', value: 'status', align: 'center' },
|
|
|
|
|
+ { text: '最后更新时间', value: 'last_updated_at' },
|
|
|
|
|
+ { text: '操作', value: 'actions', align: 'center' }
|
|
|
],
|
|
],
|
|
|
items: [],
|
|
items: [],
|
|
|
total: 0,
|
|
total: 0,
|
|
|
pageInfo: {
|
|
pageInfo: {
|
|
|
- size: 10,
|
|
|
|
|
|
|
+ size: 20,
|
|
|
current: 1
|
|
current: 1
|
|
|
},
|
|
},
|
|
|
query: {},
|
|
query: {},
|
|
|
- orders: []
|
|
|
|
|
|
|
+ previewDialog: {
|
|
|
|
|
+ show: false,
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+ limit: 200,
|
|
|
|
|
+ currentItem: null,
|
|
|
|
|
+ data: {
|
|
|
|
|
+ product: null,
|
|
|
|
|
+ columns: [],
|
|
|
|
|
+ data: [],
|
|
|
|
|
+ total_count: 0,
|
|
|
|
|
+ preview_count: 0,
|
|
|
|
|
+ error: null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ registerDialog: {
|
|
|
|
|
+ show: false,
|
|
|
|
|
+ isEdit: false,
|
|
|
|
|
+ currentId: null,
|
|
|
|
|
+ form: {}
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
created () {
|
|
created () {
|
|
|
- // this.init()
|
|
|
|
|
|
|
+ this.init()
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
methods: {
|
|
|
async init () {
|
|
async init () {
|
|
|
this.loading = true
|
|
this.loading = true
|
|
|
try {
|
|
try {
|
|
|
- const { data } = await api.getMetaDataList({ ...this.pageInfo, ...this.query })
|
|
|
|
|
- this.total = data.total
|
|
|
|
|
- this.items = data.records
|
|
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ page: this.pageInfo.current,
|
|
|
|
|
+ page_size: this.pageInfo.size,
|
|
|
|
|
+ ...this.query
|
|
|
|
|
+ }
|
|
|
|
|
+ // 移除空值
|
|
|
|
|
+ Object.keys(params).forEach(key => {
|
|
|
|
|
+ if (params[key] === '' || params[key] === null || params[key] === undefined) {
|
|
|
|
|
+ delete params[key]
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ const { data } = await api.getProducts(params)
|
|
|
|
|
+ this.total = data.pagination.total
|
|
|
|
|
+ this.items = data.list
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
this.$snackbar.error(error)
|
|
this.$snackbar.error(error)
|
|
|
} finally {
|
|
} finally {
|
|
|
this.loading = false
|
|
this.loading = false
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- handleSort (val) {
|
|
|
|
|
- this.orders = val
|
|
|
|
|
- this.init()
|
|
|
|
|
- },
|
|
|
|
|
handleSearch (obj) {
|
|
handleSearch (obj) {
|
|
|
Object.assign(this.query, obj)
|
|
Object.assign(this.query, obj)
|
|
|
this.pageInfo.current = 1
|
|
this.pageInfo.current = 1
|
|
@@ -99,11 +220,174 @@ export default {
|
|
|
pageHandleChange (index) {
|
|
pageHandleChange (index) {
|
|
|
this.pageInfo.current = index
|
|
this.pageInfo.current = index
|
|
|
this.init()
|
|
this.init()
|
|
|
|
|
+ },
|
|
|
|
|
+ // 预览数据
|
|
|
|
|
+ async handlePreview (item) {
|
|
|
|
|
+ this.previewDialog.show = true
|
|
|
|
|
+ this.previewDialog.loading = true
|
|
|
|
|
+ this.previewDialog.limit = 200
|
|
|
|
|
+ this.previewDialog.currentItem = item
|
|
|
|
|
+ this.previewDialog.data = {
|
|
|
|
|
+ product: null,
|
|
|
|
|
+ columns: [],
|
|
|
|
|
+ data: [],
|
|
|
|
|
+ total_count: 0,
|
|
|
|
|
+ preview_count: 0,
|
|
|
|
|
+ error: null
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { data } = await api.getProductPreview(item.id, this.previewDialog.limit)
|
|
|
|
|
+ this.previewDialog.data = data
|
|
|
|
|
+ // 标记为已查看
|
|
|
|
|
+ if (item.has_new_data) {
|
|
|
|
|
+ item.has_new_data = false
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ this.$snackbar.error(error)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.previewDialog.loading = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 加载更多数据
|
|
|
|
|
+ async handleLoadMore () {
|
|
|
|
|
+ this.previewDialog.limit = Math.min(this.previewDialog.limit + 200, 1000)
|
|
|
|
|
+ this.previewDialog.loading = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { data } = await api.getProductPreview(this.previewDialog.data.product.id, this.previewDialog.limit)
|
|
|
|
|
+ this.previewDialog.data = data
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ this.$snackbar.error(error)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.previewDialog.loading = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 从预览弹窗下载
|
|
|
|
|
+ async handleDownloadFromPreview () {
|
|
|
|
|
+ if (!this.previewDialog.currentItem) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ await this.handleDownload(this.previewDialog.currentItem)
|
|
|
|
|
+ },
|
|
|
|
|
+ // 下载数据
|
|
|
|
|
+ async handleDownload (item) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await api.downloadExcel(item.id, 200)
|
|
|
|
|
+
|
|
|
|
|
+ // 响应拦截器已处理错误情况,这里response格式为 { data: blob, name: filename }
|
|
|
|
|
+ const filename = response.name || `${item.product_name_en}_${new Date().toISOString().slice(0, 10).replace(/-/g, '')}.xlsx`
|
|
|
|
|
+
|
|
|
|
|
+ // 创建下载链接
|
|
|
|
|
+ const blob = new Blob([response.data], {
|
|
|
|
|
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
|
|
|
+ })
|
|
|
|
|
+ const url = window.URL.createObjectURL(blob)
|
|
|
|
|
+ const link = document.createElement('a')
|
|
|
|
|
+ link.href = url
|
|
|
|
|
+ link.download = filename
|
|
|
|
|
+ link.click()
|
|
|
|
|
+ window.URL.revokeObjectURL(url)
|
|
|
|
|
+
|
|
|
|
|
+ // 标记为已查看
|
|
|
|
|
+ if (item.has_new_data) {
|
|
|
|
|
+ item.has_new_data = false
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$snackbar.success('下载成功')
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ this.$snackbar.error(error)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 刷新数据产品
|
|
|
|
|
+ async handleRefresh (item) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { data } = await api.refreshStats(item.id)
|
|
|
|
|
+ // 更新本地数据
|
|
|
|
|
+ const index = this.items.findIndex(p => p.id === item.id)
|
|
|
|
|
+ if (index !== -1) {
|
|
|
|
|
+ Object.assign(this.items[index], data)
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$snackbar.success(`刷新成功,共 ${data.record_count} 条记录`)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ this.$snackbar.error(error)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 删除数据产品
|
|
|
|
|
+ async handleDelete (item) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.$confirm('删除确认', '确定要删除该数据产品吗?此操作不可恢复。')
|
|
|
|
|
+ await api.deleteProduct(item.id)
|
|
|
|
|
+ this.$snackbar.success('删除成功')
|
|
|
|
|
+ // 从本地列表移除
|
|
|
|
|
+ this.items = this.items.filter(p => p.id !== item.id)
|
|
|
|
|
+ this.total--
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ if (error !== 'cancel') {
|
|
|
|
|
+ this.$snackbar.error(error)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 注册数据产品
|
|
|
|
|
+ handleAdd (item = null) {
|
|
|
|
|
+ this.registerDialog.show = true
|
|
|
|
|
+ if (item) {
|
|
|
|
|
+ // 回显数据
|
|
|
|
|
+ this.registerDialog.isEdit = true
|
|
|
|
|
+ this.registerDialog.currentId = item.id
|
|
|
|
|
+ this.registerDialog.form = {
|
|
|
|
|
+ id: item.id,
|
|
|
|
|
+ product_name: item.product_name || '',
|
|
|
|
|
+ product_name_en: item.product_name_en || '',
|
|
|
|
|
+ target_table: item.target_table || '',
|
|
|
|
|
+ target_schema: item.target_schema || 'public',
|
|
|
|
|
+ description: item.description || '',
|
|
|
|
|
+ source_dataflow_id: item.source_dataflow_id || null,
|
|
|
|
|
+ source_dataflow_name: item.source_dataflow_name || ''
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 新增模式
|
|
|
|
|
+ this.registerDialog.isEdit = false
|
|
|
|
|
+ this.registerDialog.currentId = null
|
|
|
|
|
+ this.registerDialog.form = {}
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ if (this.$refs.registerForm) {
|
|
|
|
|
+ this.$refs.registerForm.resetValidation()
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ async handleRegisterSubmit () {
|
|
|
|
|
+ const formData = this.$refs.registerForm?.getValue()
|
|
|
|
|
+ if (!formData) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ await api.registerProduct(formData)
|
|
|
|
|
+ this.$snackbar.success(this.registerDialog.isEdit ? '更新成功' : '注册成功')
|
|
|
|
|
+ this.registerDialog.show = false
|
|
|
|
|
+ // 刷新列表
|
|
|
|
|
+ this.init()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ this.$snackbar.error(error)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ getStatusColor (status) {
|
|
|
|
|
+ const colors = {
|
|
|
|
|
+ active: 'success',
|
|
|
|
|
+ inactive: 'info',
|
|
|
|
|
+ error: 'error'
|
|
|
|
|
+ }
|
|
|
|
|
+ return colors[status] || 'info'
|
|
|
|
|
+ },
|
|
|
|
|
+ getStatusText (status) {
|
|
|
|
|
+ const texts = {
|
|
|
|
|
+ active: '活跃',
|
|
|
|
|
+ inactive: '非活跃',
|
|
|
|
|
+ error: '错误'
|
|
|
|
|
+ }
|
|
|
|
|
+ return texts[status] || status
|
|
|
|
|
+ },
|
|
|
|
|
+ formatDateTime (dateTime) {
|
|
|
|
|
+ return formatDate(dateTime)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
-
|
|
|
|
|
-<style lang="scss" scoped>
|
|
|
|
|
-
|
|
|
|
|
-</style>
|
|
|