| @@ -54,6 +54,7 @@ | |||||
| "vue": "2.6.11", | "vue": "2.6.11", | ||||
| "vue-bar-graph": "1.2.0", | "vue-bar-graph": "1.2.0", | ||||
| "vue-calendar-heatmap": "0.8.4", | "vue-calendar-heatmap": "0.8.4", | ||||
| "vue-i18n": "6.1.3", | |||||
| "vue-loader": "15.9.2", | "vue-loader": "15.9.2", | ||||
| "vue-router": "3.3.4", | "vue-router": "3.3.4", | ||||
| "vue-template-compiler": "2.6.11", | "vue-template-compiler": "2.6.11", | ||||
| @@ -12,13 +12,22 @@ const ( | |||||
| ) | ) | ||||
| func GetQueuePage(ctx *context.Context) { | func GetQueuePage(ctx *context.Context) { | ||||
| ctx.Data["PageIsAdmin"] = true | |||||
| ctx.Data["PageIsAdminResources"] = true | |||||
| ctx.Data["PageIsAdminResourcesQueue"] = true | |||||
| ctx.HTML(200, tplResourceQueue) | ctx.HTML(200, tplResourceQueue) | ||||
| } | } | ||||
| func GetSpecificationPage(ctx *context.Context) { | func GetSpecificationPage(ctx *context.Context) { | ||||
| ctx.Data["PageIsAdmin"] = true | |||||
| ctx.Data["PageIsAdminResources"] = true | |||||
| ctx.Data["PageIsAdminResourcesSpecification"] = true | |||||
| ctx.HTML(200, tplResourceSpecification) | ctx.HTML(200, tplResourceSpecification) | ||||
| } | } | ||||
| func GetScenePage(ctx *context.Context) { | func GetScenePage(ctx *context.Context) { | ||||
| ctx.Data["PageIsAdmin"] = true | |||||
| ctx.Data["PageIsAdminResources"] = true | |||||
| ctx.Data["PageIsAdminResourcesScene"] = true | |||||
| ctx.HTML(200, tplResourceScene) | ctx.HTML(200, tplResourceScene) | ||||
| } | } | ||||
| @@ -30,13 +30,13 @@ | |||||
| <a class="item item-first" href="javascript:void;"> | <a class="item item-first" href="javascript:void;"> | ||||
| {{.i18n.Tr "admin.resource_management"}} | {{.i18n.Tr "admin.resource_management"}} | ||||
| </a> | </a> | ||||
| <a class="item item-next" href="javascript:void;"> | |||||
| <a class="{{if .PageIsAdminResourcesQueue}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/resources/queue"> | |||||
| {{.i18n.Tr "admin.resource_pool"}} | {{.i18n.Tr "admin.resource_pool"}} | ||||
| </a> | </a> | ||||
| <a class="item item-next" href="javascript:void;"> | |||||
| <a class="{{if .PageIsAdminResourcesSpecification}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/resources/specification"> | |||||
| {{.i18n.Tr "admin.resource_price"}} | {{.i18n.Tr "admin.resource_price"}} | ||||
| </a> | </a> | ||||
| <a class="item item-next" href="javascript:void;"> | |||||
| <a class="{{if .PageIsAdminResourcesScene}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/resources/scene"> | |||||
| {{.i18n.Tr "admin.application_scenario"}} | {{.i18n.Tr "admin.application_scenario"}} | ||||
| </a> | </a> | ||||
| <a class="item item-first" href="javascript:void;"> | <a class="item item-first" href="javascript:void;"> | ||||
| @@ -0,0 +1,10 @@ | |||||
| {{template "base/head" .}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-resources-queue.css?v={{MD5 AppVer}}" /> | |||||
| <div class="admin resource"> | |||||
| {{template "admin/navbar" .}} | |||||
| <div class="ui container"> | |||||
| <div id="__vue-root"></div> | |||||
| </duv> | |||||
| </div> | |||||
| <script src="{{StaticUrlPrefix}}/js/vp-resources-queue.js?v={{MD5 AppVer}}"></script> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,10 @@ | |||||
| {{template "base/head" .}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-resources-scene.css?v={{MD5 AppVer}}" /> | |||||
| <div class="admin resource"> | |||||
| {{template "admin/navbar" .}} | |||||
| <div class="ui container"> | |||||
| <div id="__vue-root"></div> | |||||
| </duv> | |||||
| </div> | |||||
| <script src="{{StaticUrlPrefix}}/js/vp-resources-scene.js?v={{MD5 AppVer}}"></script> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,10 @@ | |||||
| {{template "base/head" .}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-resources-specification.css?v={{MD5 AppVer}}" /> | |||||
| <div class="admin resource"> | |||||
| {{template "admin/navbar" .}} | |||||
| <div class="ui container"> | |||||
| <div id="__vue-root"></div> | |||||
| </duv> | |||||
| </div> | |||||
| <script src="{{StaticUrlPrefix}}/js/vp-resources-specification.js?v={{MD5 AppVer}}"></script> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,41 @@ | |||||
| import service from '../service'; | |||||
| // 算力积分概要 | |||||
| export const getPointAccount = () => { | |||||
| return service({ | |||||
| url: '/reward/point/account', | |||||
| method: 'get', | |||||
| params: {}, | |||||
| }); | |||||
| } | |||||
| // 算力积分获取、消耗明细 | |||||
| // operate-INCREASE 表示获取明细 DECREASE表示消耗明细, page-当前页, pageSize-每页条数 | |||||
| export const getPointList = (params) => { | |||||
| return service({ | |||||
| url: '/reward/point/record/list', | |||||
| method: 'get', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 管理员充值、扣减用户积分 | |||||
| // TargetUserId, OperateType-INCREASE,DECREASE, Amount, Remark, RewardType-POINT | |||||
| export const setPointOperate = (data) => { | |||||
| return service({ | |||||
| url: '/operation/reward/point/account/operate', | |||||
| method: 'post', | |||||
| data, | |||||
| params: {} | |||||
| }); | |||||
| } | |||||
| // 算力积分页面 | |||||
| export const getPoint = () => { | |||||
| return service({ | |||||
| url: '/reward/point', | |||||
| method: 'get', | |||||
| params: {}, | |||||
| data: {}, | |||||
| }); | |||||
| } | |||||
| @@ -0,0 +1,26 @@ | |||||
| import axios from 'axios'; | |||||
| const service = axios.create({ | |||||
| baseURL: '/', | |||||
| timeout: 20000, | |||||
| }); | |||||
| service.interceptors.request.use((config) => { | |||||
| config.data && Object.assign(config.data, { | |||||
| _csrf: window.config ? window.config.csrf : '', | |||||
| }); | |||||
| config.params && Object.assign(config.params, { | |||||
| _csrf: window.config ? window.config.csrf : '', | |||||
| }); | |||||
| return config; | |||||
| }, (error) => { | |||||
| return Promise.reject(error); | |||||
| }); | |||||
| service.interceptors.response.use((response) => { | |||||
| return response; | |||||
| }, (error) => { | |||||
| return Promise.reject(error); | |||||
| }); | |||||
| export default service; | |||||
| @@ -0,0 +1,113 @@ | |||||
| <template> | |||||
| <div class="base-dlg"> | |||||
| <el-dialog | |||||
| :visible.sync="dialogShow" | |||||
| :title="title" | |||||
| :width="width" | |||||
| :fullscreen="fullscreen" | |||||
| :top="top" | |||||
| :modal="modal" | |||||
| :modal-append-to-body="modalAppendToBody" | |||||
| :append-to-body="appendToBody" | |||||
| :lock-scroll="lockScroll" | |||||
| :custom-class="customClass" | |||||
| :close-on-click-modal="closeOnClickModal" | |||||
| :close-on-press-escape="closeOnPressEscape" | |||||
| :show-close="showClose" | |||||
| :center="center" | |||||
| :destroy-on-close="destroyOnClose" | |||||
| :before-close="beforeClose" | |||||
| @open="open" | |||||
| @opened="opened" | |||||
| @close="close" | |||||
| @closed="closed" | |||||
| > | |||||
| <template v-slot:title> | |||||
| <slot name="title"></slot> | |||||
| </template> | |||||
| <template v-slot:default> | |||||
| <slot name="default"></slot> | |||||
| </template> | |||||
| <template v-slot:footer> | |||||
| <slot name="footer"></slot> | |||||
| </template> | |||||
| </el-dialog> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| export default { | |||||
| name: "BaseDialog", | |||||
| props: { | |||||
| visible: { type: Boolean, default: false }, | |||||
| title: { type: String, default: "" }, | |||||
| width: { type: String, default: "" }, | |||||
| fullscreen: { type: Boolean, default: false }, | |||||
| top: { type: String }, | |||||
| modal: { type: Boolean, default: true }, | |||||
| modalAppendToBody: { type: Boolean, default: true }, | |||||
| appendToBody: { type: Boolean, default: false }, | |||||
| lockScroll: { type: Boolean, default: false }, | |||||
| customClass: { type: String, default: "" }, | |||||
| closeOnClickModal: { type: Boolean, default: false }, | |||||
| closeOnPressEscape: { type: Boolean, default: true }, | |||||
| showClose: { type: Boolean, default: true }, | |||||
| beforeClose: { type: Function }, | |||||
| center: { type: Boolean, default: false }, | |||||
| destroyOnClose: { type: Boolean, default: false }, | |||||
| }, | |||||
| data: function () { | |||||
| return { | |||||
| dialogShow: false, | |||||
| }; | |||||
| }, | |||||
| watch: { | |||||
| visible: function (val) { | |||||
| this.dialogShow = val; | |||||
| }, | |||||
| }, | |||||
| methods: { | |||||
| open: function () { | |||||
| this.$emit("open"); | |||||
| }, | |||||
| opened: function () { | |||||
| this.$emit("opened"); | |||||
| }, | |||||
| close: function () { | |||||
| this.$emit("close"); | |||||
| }, | |||||
| closed: function () { | |||||
| this.$emit("closed"); | |||||
| this.$emit("update:visible", false); | |||||
| }, | |||||
| }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .base-dlg { | |||||
| /deep/ .el-dialog__header { | |||||
| text-align: left; | |||||
| height: 45px; | |||||
| background: rgb(240, 240, 240); | |||||
| border-radius: 5px 5px 0px 0px; | |||||
| border-bottom: 1px solid rgb(212, 212, 213); | |||||
| padding: 0 15px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| font-weight: 500; | |||||
| font-size: 16px; | |||||
| color: rgb(16, 16, 16); | |||||
| .el-dialog__title { | |||||
| font-weight: 500; | |||||
| font-size: 16px; | |||||
| color: rgb(16, 16, 16); | |||||
| } | |||||
| .el-dialog__headerbtn { | |||||
| top: 15px; | |||||
| right: 15px; | |||||
| } | |||||
| } | |||||
| /deep/ .el-dialog__body { | |||||
| padding: 15px 15px; | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,9 @@ | |||||
| import { i18n } from '~/langs'; | |||||
| export const SOURCE_TYPE = [{ k: 'ACCOMPLISH_TASK', v: i18n.t('accomplishTask') }, { k: 'ADMIN_OPERATE', v: i18n.t('adminOperate') }, { k: 'RUN_CLOUDBRAIN_TASK', v: i18n.t('runCloudBrainTask') }]; | |||||
| export const CONSUME_STATUS = [{ k: 'OPERATING', v: i18n.t('operating') }, { k: 'SUCCEEDED', v: i18n.t('succeeded') }]; | |||||
| export const POINT_ACTIONS = [ | |||||
| { k: 1, v: i18n.t('createPublicProject') }, { k: 6, v: i18n.t('dailyPutforwardTasks') }, { k: 7, v: i18n.t('dailyPR') }, { k: 10, v: i18n.t('comment') }, { k: 24, v: i18n.t('uploadDatasetFile') }, { k: 30, v: i18n.t('importNewModel') }, { k: 34, v: i18n.t('completeWechatCodeScanningVerification') }, | |||||
| { k: 35, v: i18n.t('dailyRunCloudbrainTasks') }, { k: 36, v: i18n.t('datasetRecommendedByThePlatform') }, { k: 37, v: i18n.t('submitNewPublicImage') }, { k: 38, v: i18n.t('imageRecommendedByThePlatform') }, { k: 39, v: i18n.t('firstChangeofAvatar') }, { k: 40, v: i18n.t('dailyCommit') }, | |||||
| ]; | |||||
| export const JOB_TYPE = [{ k: 'DEBUG', v: i18n.t('debugTask') }, { k: 'TRAIN', v: i18n.t('trainTask') }, { k: 'INFERENCE', v: i18n.t('inferenceTask') }, { k: 'BENCHMARK', v: i18n.t('benchmarkTask') }]; | |||||
| @@ -0,0 +1,70 @@ | |||||
| const en = { | |||||
| loading: 'Loading...', | |||||
| noData: 'No Data', | |||||
| date: 'Date', | |||||
| accomplishTask: 'Accomplish Task', | |||||
| adminOperate: 'Administrator Operation', | |||||
| runCloudBrainTask: 'Run CloudBrain Task', | |||||
| operating: 'Operating', | |||||
| succeeded: 'Succeeded', | |||||
| debugTask: 'Debug Task', | |||||
| trainTask: 'Train Task', | |||||
| inferenceTask: 'Inference Task', | |||||
| benchmarkTask: 'Benchmark Task', | |||||
| createPublicProject: 'Create Public Projects', | |||||
| dailyPutforwardTasks: 'Daily Put Forward Tasks', | |||||
| dailyPR: 'Daily PR', | |||||
| comment: 'Comment', | |||||
| uploadDatasetFile: 'Upload Dataset Files', | |||||
| importNewModel: 'Import New Models', | |||||
| completeWechatCodeScanningVerification: 'Complete Wechat Code Scanning Verification', | |||||
| dailyRunCloudbrainTasks: 'Daily Run Cloudbrain Tasks', | |||||
| datasetRecommendedByThePlatform: 'Dataset Recommended by the Platform', | |||||
| submitNewPublicImage: 'Submit New Public Images', | |||||
| imageRecommendedByThePlatform: 'Image Recommended by the Platform', | |||||
| firstChangeofAvatar: 'First Change of Avatar', | |||||
| dailyCommit: 'Daily Commit', | |||||
| calcPointDetails: 'Calculation Points Details', | |||||
| calcPointAcquisitionInstructions: 'Calculation Points Acquisition Instructions', | |||||
| CurrAvailableCalcPoints: 'Currently Available Calculation Points', | |||||
| totalGainCalcPoints: 'Total Gain of Calculation Points', | |||||
| totalConsumeCalcPoints: 'Total Consume of Calculation Points', | |||||
| gainDetail: 'Gain Detail', | |||||
| consumeDetail: 'Consume Detail', | |||||
| serialNumber: 'Serial Number', | |||||
| time: 'Time', | |||||
| scene: 'Scene', | |||||
| behaviorOfPoint: 'Behavior Of Point', | |||||
| explanation: 'Explanation', | |||||
| points: 'Points', | |||||
| status: 'Status', | |||||
| runTime: 'Run Time', | |||||
| taskName: 'Task Name', | |||||
| createdRepository: 'created repository ', | |||||
| openedIssue: 'opened issue ', | |||||
| createdPullRequest: 'created pull request ', | |||||
| commentedOnIssue: 'commented on issue ', | |||||
| uploadDataset: 'upload dataset ', | |||||
| createdNewModel: 'created new model ', | |||||
| firstBindingWechatRewards: 'first binding wechat rewards', | |||||
| created: 'created ', | |||||
| type: ' type ', | |||||
| dataset: 'dataset ', | |||||
| setAsRecommendedDataset: ' was set as recommended dataset', | |||||
| committedImage: 'committed image ', | |||||
| image: 'image ', | |||||
| setAsRecommendedImage: ' was set as recommended image', | |||||
| updatedAvatar: 'updated avatar', | |||||
| pushedBranch: 'pushed to {branch} at ', | |||||
| dailyMaxTips: `can't get full points when reach the daily upper limit`, | |||||
| memory: 'Memory', | |||||
| sharedMemory: 'Shared Memory', | |||||
| ';': ', ', | |||||
| noPointGainRecord: 'No Point Earn Record Yet', | |||||
| noPointConsumeRecord: 'No Point Consume Record Yet', | |||||
| } | |||||
| export default en; | |||||
| @@ -0,0 +1,70 @@ | |||||
| const zh = { | |||||
| loading: '加载中...', | |||||
| noData: '暂无数据', | |||||
| date: '日期', | |||||
| accomplishTask: '积分任务', | |||||
| adminOperate: '管理员操作', | |||||
| runCloudBrainTask: '运行云脑任务', | |||||
| operating: '消耗中', | |||||
| succeeded: '已完成', | |||||
| debugTask: '调试任务', | |||||
| trainTask: '训练任务', | |||||
| inferenceTask: '推理任务', | |||||
| benchmarkTask: '评测任务', | |||||
| createPublicProject: '创建公开项目', | |||||
| dailyPutforwardTasks: '每日提出任务', | |||||
| dailyPR: '每日提出PR', | |||||
| comment: '发表评论', | |||||
| uploadDatasetFile: '上传数据集文件', | |||||
| importNewModel: '导入新模型', | |||||
| completeWechatCodeScanningVerification: '完成微信扫码验证', | |||||
| dailyRunCloudbrainTasks: '每日运行云脑任务', | |||||
| datasetRecommendedByThePlatform: '数据集被平台推荐', | |||||
| submitNewPublicImage: '提交新公开镜像', | |||||
| imageRecommendedByThePlatform: '镜像被平台推荐', | |||||
| firstChangeofAvatar: '首次更换头像', | |||||
| dailyCommit: '每日commit', | |||||
| calcPointDetails: '算力积分明细', | |||||
| calcPointAcquisitionInstructions: '积分获取说明', | |||||
| CurrAvailableCalcPoints: '当前可用算力积分(分)', | |||||
| totalGainCalcPoints: '总获取算力积分(分)', | |||||
| totalConsumeCalcPoints: '总消耗算力积分(分)', | |||||
| gainDetail: '获取明细', | |||||
| consumeDetail: '消耗明细', | |||||
| serialNumber: '流水号', | |||||
| time: '时间', | |||||
| scene: '场景', | |||||
| behaviorOfPoint: '积分行为', | |||||
| explanation: '说明', | |||||
| points: '积分', | |||||
| status: '状态', | |||||
| runTime: '运行时长', | |||||
| taskName: '任务名称', | |||||
| createdRepository: '创建了项目', | |||||
| openedIssue: '创建了任务', | |||||
| createdPullRequest: '创建了合并请求', | |||||
| commentedOnIssue: '评论了任务', | |||||
| uploadDataset: '上传了数据集文件', | |||||
| createdNewModel: '导入了新模型', | |||||
| firstBindingWechatRewards: '首次绑定微信奖励', | |||||
| created: '创建了', | |||||
| type: '类型', | |||||
| dataset: '数据集', | |||||
| setAsRecommendedDataset: '被设置为推荐数据集', | |||||
| committedImage: '提交了镜像', | |||||
| image: '镜像', | |||||
| setAsRecommendedImage: '被设置为推荐镜像', | |||||
| updatedAvatar: '更新了头像', | |||||
| pushedBranch: '推送了{branch}分支代码到', | |||||
| dailyMaxTips: '达到每日上限积分,不能拿满分', | |||||
| memory: '内存', | |||||
| sharedMemory: '共享内存', | |||||
| ';': ';', | |||||
| noPointGainRecord: '还没有积分获取记录', | |||||
| noPointConsumeRecord: '还没有积分消耗记录', | |||||
| } | |||||
| export default zh; | |||||
| @@ -0,0 +1,16 @@ | |||||
| import Vue from 'vue'; | |||||
| import VueI18n from 'vue-i18n'; | |||||
| import jsCookie from 'js-cookie'; | |||||
| import zh from './config/zh-CN'; | |||||
| import en from './config/en-US'; | |||||
| Vue.use(VueI18n); | |||||
| export const lang = jsCookie.get('lang') || 'zh-CN'; | |||||
| export const i18n = new VueI18n({ | |||||
| locale: lang, | |||||
| messages: { | |||||
| 'zh-CN': zh, | |||||
| 'en-US': en | |||||
| }, | |||||
| }); | |||||
| @@ -0,0 +1,196 @@ | |||||
| <template> | |||||
| <div class="base-dlg"> | |||||
| <BaseDialog :visible.sync="dialogShow" :width="`750px`" :title="type === 'add' ? `新建资源池(队列)` : '修改资源池(队列)'" | |||||
| @open="open" @opened="opened" @close="close" @closed="closed"> | |||||
| <div class="dlg-content"> | |||||
| <div class="form"> | |||||
| <div class="form-row"> | |||||
| <div class="title required"> | |||||
| <span>资源池(队列)名称</span> | |||||
| </div> | |||||
| <div class="content"> | |||||
| <el-input v-model="point" placeholder=""></el-input> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row"> | |||||
| <div class="title required"> | |||||
| <span>所属集群</span> | |||||
| </div> | |||||
| <div class="content"> | |||||
| <el-select :value="selectValue"></el-select> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row"> | |||||
| <div class="title required"> | |||||
| <span>智算中心</span> | |||||
| </div> | |||||
| <div class="content"> | |||||
| <el-select :value="selectValue"></el-select> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row"> | |||||
| <div class="title required"> | |||||
| <span>计算资源</span> | |||||
| </div> | |||||
| <div class="content"> | |||||
| <el-select :value="selectValue"></el-select> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row"> | |||||
| <div class="title required"> | |||||
| <span>卡类型</span> | |||||
| </div> | |||||
| <div class="content"> | |||||
| <el-select :value="selectValue"></el-select> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row"> | |||||
| <div class="title required"> | |||||
| <span>卡数</span> | |||||
| </div> | |||||
| <div class="content"> | |||||
| <el-input v-model="point" type="number" placeholder=""></el-input> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row" style="margin-top: 10px"> | |||||
| <div class="title"><span>备注</span></div> | |||||
| <div class="content" style="width: 400px"> | |||||
| <el-input type="textarea" :autosize="{ minRows: 3, maxRows: 4 }" | |||||
| :placeholder="true ? '请输入充值操作备注' : '请输入扣减操作备注'" v-model="remark"> | |||||
| </el-input> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row" style="margin-top: 20px"> | |||||
| <div class="title"></div> | |||||
| <div class="content"> | |||||
| <el-button type="primary" class="btn confirm-btn" @click="confirm">确定</el-button> | |||||
| <el-button class="btn" @click="cancel">取消</el-button> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </BaseDialog> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import BaseDialog from '~/components/BaseDialog.vue'; | |||||
| export default { | |||||
| name: "QueueDialog", | |||||
| props: { | |||||
| visible: { type: Boolean, default: false }, | |||||
| title: { type: String, default: '' }, | |||||
| type: { type: String, defalut: 'add' }, | |||||
| data: { type: Object, default: () => ({}) }, | |||||
| }, | |||||
| components: { | |||||
| BaseDialog | |||||
| }, | |||||
| data: function () { | |||||
| return { | |||||
| dialogShow: false, | |||||
| dataInfo: {}, | |||||
| point: '', | |||||
| remark: '', | |||||
| selectValue: '', | |||||
| }; | |||||
| }, | |||||
| watch: { | |||||
| visible: function (val) { | |||||
| this.dialogShow = val; | |||||
| }, | |||||
| }, | |||||
| methods: { | |||||
| open: function () { | |||||
| if (this.type === 'add') { | |||||
| } else if (this.type === 'edit') { | |||||
| } | |||||
| console.log('open', this.type, this.data); | |||||
| this.$emit("open"); | |||||
| }, | |||||
| opened: function () { | |||||
| this.$emit("opened"); | |||||
| }, | |||||
| close: function () { | |||||
| this.$emit("close"); | |||||
| }, | |||||
| closed: function () { | |||||
| this.$emit("closed"); | |||||
| this.$emit("update:visible", false); | |||||
| }, | |||||
| confirm: function () { | |||||
| }, | |||||
| cancel: function () { | |||||
| } | |||||
| }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .dlg-content { | |||||
| margin: 20px 0 25px 0; | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| .form { | |||||
| width: 600px; | |||||
| .form-row { | |||||
| display: flex; | |||||
| min-height: 42px; | |||||
| margin-bottom: 4px; | |||||
| .title { | |||||
| width: 160px; | |||||
| display: flex; | |||||
| justify-content: flex-end; | |||||
| align-items: center; | |||||
| margin-right: 20px; | |||||
| color: rgb(136, 136, 136); | |||||
| font-size: 14px; | |||||
| &.required { | |||||
| span { | |||||
| position: relative; | |||||
| } | |||||
| span::after { | |||||
| position: absolute; | |||||
| right: -10px; | |||||
| top: -2px; | |||||
| vertical-align: top; | |||||
| content: '*'; | |||||
| color: #db2828; | |||||
| } | |||||
| } | |||||
| } | |||||
| .content { | |||||
| width: 300px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| /deep/ .el-select { | |||||
| width: 100%; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .btn { | |||||
| color: rgb(2, 0, 4); | |||||
| background-color: rgb(194, 199, 204); | |||||
| border-color: rgb(194, 199, 204); | |||||
| &.confirm-btn { | |||||
| color: #fff; | |||||
| background-color: rgb(56, 158, 13); | |||||
| border-color: rgb(56, 158, 13); | |||||
| } | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,194 @@ | |||||
| <template> | |||||
| <div> | |||||
| <div class="title"><span>资源池(队列)</span></div> | |||||
| <div class="tools-bar"> | |||||
| <div> | |||||
| <el-select class="select" size="medium" :value="value1"> | |||||
| <!-- <el-option v-for="item in sceneList" :key="item.k" :label="item.v" :value="item.k" /> --> | |||||
| </el-select> | |||||
| <el-select class="select" size="medium" :value="value1"> | |||||
| <!-- <el-option v-for="item in pointActions" :key="item.k" :label="item.v" :value="item.k" /> --> | |||||
| </el-select> | |||||
| <el-select class="select" size="medium" :value="value1"></el-select> | |||||
| <el-select class="select" size="medium" :value="value1"></el-select> | |||||
| </div> | |||||
| <div> | |||||
| <el-button size="medium" icon="el-icon-refresh" @click="showDialog('edit', {x: 1, y: 2, z: 3})">同步智算网络</el-button> | |||||
| <el-button type="primary" icon="el-icon-plus" size="medium" @click="showDialog('add')">新增资源池</el-button> | |||||
| </div> | |||||
| </div> | |||||
| <div class="table-container"> | |||||
| <div> | |||||
| <el-table border :data="tableData" style="width: 100%" v-loading="loading" stripe> | |||||
| <el-table-column prop="index" label="ID" align="center" header-align="center"></el-table-column> | |||||
| <el-table-column prop="userName" label="资源池(队列)名称" align="center" header-align="center"> | |||||
| <template #default="scope"> | |||||
| <a :href="`/${scope.row.userName}`">{{ scope.row.userName }}</a> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column prop="date" label="所属集群" align="center" header-align="center"> | |||||
| </el-table-column> | |||||
| <el-table-column prop="scene" label="智算中心ID" align="center" header-align="center"> | |||||
| </el-table-column> | |||||
| <el-table-column prop="pointAction" label="智算中心" align="left" header-align="center"> | |||||
| </el-table-column> | |||||
| <el-table-column prop="remark" label="计算资源" align="left" header-align="center" min-width="200"> | |||||
| <template #default="scope"> | |||||
| <span v-html="scope.row.remark"></span> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column prop="operator" label="卡类型" align="center" header-align="center"> | |||||
| </el-table-column> | |||||
| <el-table-column prop="amount" label="卡数 " align="center" header-align="center"> | |||||
| <template #default="scope"> | |||||
| {{ scope.row.amount }} | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column prop="blance" label="最后更新时间" align="center" header-align="center"> | |||||
| <template #default="scope"> | |||||
| {{ scope.row.blance }} | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column prop="blance" label="备注" align="center" header-align="center"> | |||||
| <template #default="scope"> | |||||
| {{ scope.row.blance }} | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column prop="blance" label="操作" align="center" header-align="center"> | |||||
| <template #default="scope"> | |||||
| {{ scope.row.blance }} | |||||
| </template> | |||||
| </el-table-column> | |||||
| <template #empty> | |||||
| <span style="font-size: 12px">{{ | |||||
| loading ? '加载中...' : '暂无数据' | |||||
| }}</span> | |||||
| </template> | |||||
| </el-table> | |||||
| </div> | |||||
| <div class="__r_p_pagination"> | |||||
| <div style="margin-top: 2rem"> | |||||
| <div class="center"> | |||||
| <el-pagination background @current-change="currentChange" :current-page="pageInfo.curpage" | |||||
| :page-sizes="pageInfo.pageSizes" :page-size="pageInfo.pageSize" | |||||
| layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total"> | |||||
| </el-pagination> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <QueueDialog :visible.sync="queueDialogShow" :type="queueDialogType" :data="queueDialogData" @confirm="queueDialogConfirm"></QueueDialog> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import QueueDialog from '../components/QueueDialog.vue'; | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| value1: '1', | |||||
| searchVal: '', | |||||
| loading: false, | |||||
| tableData: [], | |||||
| pageInfo: { | |||||
| curpage: 1, | |||||
| pageSize: 10, | |||||
| pageSizes: [10], | |||||
| total: 0, | |||||
| }, | |||||
| queueDialogShow: false, | |||||
| queueDialogType: 'add', | |||||
| queueDialogData: {}, | |||||
| }; | |||||
| }, | |||||
| components: { | |||||
| QueueDialog | |||||
| }, | |||||
| methods: { | |||||
| getTableData: function () { | |||||
| this.tableData = new Array(20).fill(0).map((_, index) => { | |||||
| return { | |||||
| index: index, | |||||
| }; | |||||
| }) | |||||
| }, | |||||
| currentChange: function (val) { | |||||
| this.pageInfo.curpage = val; | |||||
| // this.getTableData(); | |||||
| }, | |||||
| showDialog(type, data) { | |||||
| this.queueDialogType = type; | |||||
| this.queueDialogData = data || {}; | |||||
| this.queueDialogShow = true; | |||||
| }, | |||||
| queueDialogConfirm() { | |||||
| this.queueDialogShow = false; | |||||
| } | |||||
| }, | |||||
| mounted: function () { | |||||
| this.getTableData(); | |||||
| }, | |||||
| beforeDestroy: function () { | |||||
| }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .title { | |||||
| height: 30px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-bottom: 5px; | |||||
| span { | |||||
| font-weight: 700; | |||||
| font-size: 16px; | |||||
| color: rgb(16, 16, 16); | |||||
| } | |||||
| } | |||||
| .tools-bar { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| margin-bottom: 10px; | |||||
| .select { | |||||
| margin-right: 10px; | |||||
| /deep/ .el-input__inner { | |||||
| border-radius: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| .table-container { | |||||
| margin-bottom: 16px; | |||||
| /deep/ .el-table__header { | |||||
| th { | |||||
| background: rgb(245, 245, 246); | |||||
| font-size: 12px; | |||||
| color: rgb(36, 36, 36); | |||||
| } | |||||
| } | |||||
| /deep/ .el-table__body { | |||||
| td { | |||||
| font-size: 12px; | |||||
| } | |||||
| } | |||||
| .op-btn { | |||||
| cursor: pointer; | |||||
| font-size: 12px; | |||||
| color: rgb(25, 103, 252); | |||||
| margin: 0 5px; | |||||
| } | |||||
| } | |||||
| .center { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,17 @@ | |||||
| import Vue from 'vue'; | |||||
| import ElementUI from 'element-ui'; | |||||
| import 'element-ui/lib/theme-chalk/index.css'; | |||||
| import localeEn from 'element-ui/lib/locale/lang/en'; | |||||
| import localeZh from 'element-ui/lib/locale/lang/zh-CN'; | |||||
| import { i18n, lang } from '~/langs'; | |||||
| import App from './index.vue'; | |||||
| Vue.use(ElementUI, { | |||||
| locale: lang === 'zh-CN' ? localeZh : localeEn, | |||||
| size: 'small', | |||||
| }); | |||||
| new Vue({ | |||||
| i18n, | |||||
| render: (h) => h(App), | |||||
| }).$mount('#__vue-root'); | |||||
| @@ -0,0 +1,43 @@ | |||||
| <template> | |||||
| <div class="xxx"> | |||||
| scene | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| loading: false, | |||||
| summaryInfo: { | |||||
| available: 0, | |||||
| gain: 0, | |||||
| used: 0, | |||||
| }, | |||||
| tabIndex: 0, | |||||
| tableData: [], | |||||
| pageInfo: { | |||||
| curpage: 1, | |||||
| pageSize: 10, | |||||
| pageSizes: [10], | |||||
| total: 0, | |||||
| }, | |||||
| eventSource: null, | |||||
| }; | |||||
| }, | |||||
| components: {}, | |||||
| methods: { | |||||
| getTableData: function () { | |||||
| }, | |||||
| }, | |||||
| mounted: function () { | |||||
| }, | |||||
| beforeDestroy: function () { | |||||
| }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| </style> | |||||
| @@ -0,0 +1,17 @@ | |||||
| import Vue from 'vue'; | |||||
| import ElementUI from 'element-ui'; | |||||
| import 'element-ui/lib/theme-chalk/index.css'; | |||||
| import localeEn from 'element-ui/lib/locale/lang/en'; | |||||
| import localeZh from 'element-ui/lib/locale/lang/zh-CN'; | |||||
| import { i18n, lang } from '~/langs'; | |||||
| import App from './index.vue'; | |||||
| Vue.use(ElementUI, { | |||||
| locale: lang === 'zh-CN' ? localeZh : localeEn, | |||||
| size: 'small', | |||||
| }); | |||||
| new Vue({ | |||||
| i18n, | |||||
| render: (h) => h(App), | |||||
| }).$mount('#__vue-root'); | |||||
| @@ -0,0 +1,43 @@ | |||||
| <template> | |||||
| <div class="xxx"> | |||||
| specification | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| loading: false, | |||||
| summaryInfo: { | |||||
| available: 0, | |||||
| gain: 0, | |||||
| used: 0, | |||||
| }, | |||||
| tabIndex: 0, | |||||
| tableData: [], | |||||
| pageInfo: { | |||||
| curpage: 1, | |||||
| pageSize: 10, | |||||
| pageSizes: [10], | |||||
| total: 0, | |||||
| }, | |||||
| eventSource: null, | |||||
| }; | |||||
| }, | |||||
| components: {}, | |||||
| methods: { | |||||
| getTableData: function () { | |||||
| }, | |||||
| }, | |||||
| mounted: function () { | |||||
| }, | |||||
| beforeDestroy: function () { | |||||
| }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| </style> | |||||
| @@ -0,0 +1,17 @@ | |||||
| import Vue from 'vue'; | |||||
| import ElementUI from 'element-ui'; | |||||
| import 'element-ui/lib/theme-chalk/index.css'; | |||||
| import localeEn from 'element-ui/lib/locale/lang/en'; | |||||
| import localeZh from 'element-ui/lib/locale/lang/zh-CN'; | |||||
| import { i18n, lang } from '~/langs'; | |||||
| import App from './index.vue'; | |||||
| Vue.use(ElementUI, { | |||||
| locale: lang === 'zh-CN' ? localeZh : localeEn, | |||||
| size: 'small', | |||||
| }); | |||||
| new Vue({ | |||||
| i18n, | |||||
| render: (h) => h(App), | |||||
| }).$mount('#__vue-root'); | |||||
| @@ -0,0 +1,148 @@ | |||||
| import { formatDate } from 'element-ui/lib/utils/date-util'; | |||||
| import { SOURCE_TYPE, CONSUME_STATUS, POINT_ACTIONS, JOB_TYPE } from '~/const'; | |||||
| import { i18n } from '~/langs'; | |||||
| const getSourceType = (key) => { | |||||
| const find = SOURCE_TYPE.filter(item => item.k === key); | |||||
| return find.length ? find[0].v : key; | |||||
| }; | |||||
| const getConsumeStatus = (key) => { | |||||
| const find = CONSUME_STATUS.filter(item => item.k === key); | |||||
| return find.length ? find[0].v : key; | |||||
| }; | |||||
| const getPointAction = (key) => { | |||||
| const find = POINT_ACTIONS.filter(item => item.k === key); | |||||
| return find.length ? find[0].v : key; | |||||
| }; | |||||
| const getJobType = (key) => { | |||||
| const find = JOB_TYPE.filter(item => item.k === key); | |||||
| return find.length ? find[0].v : key; | |||||
| }; | |||||
| const getJobTypeLink = (record, type) => { | |||||
| let link = type === 'INCREASE' ? record.Action.RepoLink : '/' + record.Cloudbrain.RepoFullName; | |||||
| const cloudbrain = type === 'INCREASE' ? record.Action?.Cloudbrain : record.Cloudbrain; | |||||
| switch (cloudbrain?.JobType) { | |||||
| case 'DEBUG': | |||||
| if (cloudbrain.ComputeResource === 'CPU/GPU') { | |||||
| link += `/cloudbrain/${cloudbrain.ID}`; | |||||
| } else { | |||||
| link += `/modelarts/notebook/${cloudbrain.ID}`; | |||||
| } | |||||
| break; | |||||
| case 'TRAIN': | |||||
| if (cloudbrain.Type === 1) { | |||||
| link += `/modelarts/train-job/${cloudbrain.JobID}`; | |||||
| } else if (cloudbrain.Type === 0) { | |||||
| link += `/cloudbrain/train-job/${cloudbrain.JobID}`; | |||||
| } else if (cloudbrain.Type === 2) { | |||||
| link += `/grampus/train-job/${cloudbrain.JobID}`; | |||||
| } | |||||
| break; | |||||
| case 'INFERENCE': | |||||
| link += `/modelarts/inference-job/${cloudbrain.JobID}`; | |||||
| break; | |||||
| case 'BENCHMARK': | |||||
| link += `/cloudbrain/benchmark/${cloudbrain.ID}`; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| }; | |||||
| return link; | |||||
| }; | |||||
| export const getRewardPointRecordInfo = (record) => { | |||||
| const out = { | |||||
| sn: record.SerialNo, | |||||
| date: formatDate(new Date(record.LastOperateDate * 1000), 'yyyy-MM-dd HH:mm:ss'), | |||||
| _status: record.Status, | |||||
| status: getConsumeStatus(record.Status) || '--', | |||||
| statusColor: record.Status === 'OPERATING' ? 'rgb(33, 186, 69)' : '', | |||||
| _sourceType: record.SourceType, | |||||
| sourceType: getSourceType(record.SourceType), | |||||
| duration: record?.Cloudbrain?.Duration || '--', | |||||
| taskName: record?.Cloudbrain?.DisplayJobName || '--', | |||||
| taskId: record?.Cloudbrain?.ID, | |||||
| action: record?.Action?.OpType ? getPointAction(record.Action.OpType) : '--', | |||||
| remark: record.Remark, | |||||
| amount: record.Amount, | |||||
| }; | |||||
| if (record.OperateType === 'INCREASE') { | |||||
| if (record.SourceType === 'ADMIN_OPERATE') { | |||||
| out.remark = record.Remark; | |||||
| } else if (record.SourceType === 'ACCOMPLISH_TASK') { | |||||
| switch (record?.Action?.OpType) { | |||||
| case 1: // 创建公开项目 - 创建了项目OpenI/aiforge | |||||
| out.remark = `${i18n.t('createdRepository')}<a href="${record.Action.RepoLink}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}</a>`; | |||||
| break; | |||||
| case 6: // 每日提出任务 - 创建了任务PCL-Platform.Intelligence/AISynergy#19 | |||||
| out.remark = `${i18n.t('openedIssue')}<a href="${record.Action.RepoLink}/issues/${record.Action.IssueInfos[0]}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}#${record.Action.IssueInfos[0]}</a>`; | |||||
| break; | |||||
| case 7: // 每日提出PR - 创建了合并请求OpenI/aiforge#1 | |||||
| out.remark = `${i18n.t('createdPullRequest')}<a href="${record.Action.RepoLink}/pulls/${record.Action.IssueInfos[0]}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}#${record.Action.IssueInfos[0]}</a>`; | |||||
| break; | |||||
| case 10: // 发表评论 - 评论了任务PCL-Platform.Intelligence/AISynergy#19 | |||||
| out.remark = `${i18n.t('commentedOnIssue')}<a href="${record.Action.CommentLink}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}#${record.Action.IssueInfos[0]}</a>`; | |||||
| break; | |||||
| case 24: // 上传数据集文件 - 上传了数据集文件MMISTData.zip | |||||
| out.remark = `${i18n.t('uploadDataset')}<a href="${record.Action.RepoLink}/datasets" rel="nofollow">${record.Action.RefName}</a>`; | |||||
| break; | |||||
| case 30: // 导入新模型 - 导入了新模型resnet50_qx7l | |||||
| out.remark = `${i18n.t('createdNewModel')}<a href="${record.Action.RepoLink}/modelmanage/show_model_info?name=${record.Action.RefName}" rel="nofollow">${record.Action.RefName}</a>`; | |||||
| break; | |||||
| case 34: // 完成微信扫码验证 - 首次绑定微信奖励 | |||||
| out.remark = `${i18n.t('firstBindingWechatRewards')}`; | |||||
| break; | |||||
| case 35: // 每日运行云脑任务 - 创建了(CPU/GPU/NPU)类型(调试/训练/推理/评测)任务tangl202204131431995 | |||||
| out.remark = `${i18n.t('created')}${record.Action?.Cloudbrain?.ComputeResource}${i18n.t('type')}${getJobType(record.Action?.Cloudbrain?.JobType)} <a href="${getJobTypeLink(record, 'INCREASE')}" rel="nofollow">${record.Action.RefName}</a>`; | |||||
| break; | |||||
| case 36: // 数据集被平台推荐 - 数据集XXX被设置为推荐数据集 | |||||
| out.remark = `${i18n.t('dataset')}<a href="${record.Action.RepoLink}/datasets" rel="nofollow">${record.Action.Content && record.Action.Content.split('|')[1]}</a>${i18n.t('setAsRecommendedDataset')}`; | |||||
| break; | |||||
| case 37: // 提交新公开镜像 - 提交了镜像jiangxiang_ceshi_tang03 | |||||
| out.remark = `${i18n.t('committedImage')}<span style="font-weight:bold;">${record.Action.Content && record.Action.Content.split('|')[1]}</span>`; | |||||
| break; | |||||
| case 38: // 镜像被平台推荐 - 镜像XXX被设置为推荐镜像 | |||||
| out.remark = `${i18n.t('image')}<span style="font-weight:bold;">${record.Action.Content && record.Action.Content.split('|')[1]}</span>${i18n.t('setAsRecommendedImage')}`; | |||||
| break; | |||||
| case 39: // 首次更换头像 - 更新了头像 | |||||
| out.remark = `${i18n.t('updatedAvatar')}`; | |||||
| break; | |||||
| case 40: // 每日commit - 推送了xxxx分支的代码到OpenI/aiforge | |||||
| const words = record.Action.RefName.split('/'); | |||||
| const branch = words[words.length - 1]; | |||||
| out.remark = `${i18n.t('pushedBranch', { | |||||
| branch: `<a href="${record.Action.RepoLink}/src/branch/${branch}" rel="nofollow">${branch}</a>` | |||||
| })}<a href="${record.Action.RepoLink}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}</a>`; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| } else if (record.SourceType === 'RUN_CLOUDBRAIN_TASK') { | |||||
| // | |||||
| } | |||||
| if (record.LossAmount !== 0) { | |||||
| out.amount = record.Amount; | |||||
| out.remark += `${out.remark ? i18n.t(';') : ''}${i18n.t('dailyMaxTips')}`; | |||||
| } | |||||
| } else if (record.OperateType === 'DECREASE') { | |||||
| if (record.SourceType === 'ADMIN_OPERATE') { | |||||
| out.remark = record.Remark; | |||||
| } else if (record.SourceType === 'ACCOMPLISH_TASK') { | |||||
| // | |||||
| } else if (record.SourceType === 'RUN_CLOUDBRAIN_TASK') { | |||||
| out.taskName = `<a href="${getJobTypeLink(record, 'DECREASE')}" rel="nofollow">${record?.Cloudbrain?.DisplayJobName}</a>`; | |||||
| if (record?.Cloudbrain?.ComputeResource === 'CPU/GPU') { | |||||
| const resourceSpec = record?.Cloudbrain?.ResourceSpec?.ResourceSpec; | |||||
| out.remark = `【${getJobType(record?.Cloudbrain?.JobType)}】【${record?.Cloudbrain?.ComputeResource}】【GPU: ${resourceSpec?.gpu}, CPU: ${resourceSpec?.cpu}, ${i18n.t('memory')}: ${(resourceSpec?.memMiB / 1024).toFixed(2)}GB, ${i18n.t('sharedMemory')}: ${(resourceSpec?.shareMemMiB / 1024).toFixed(2)}GB】`; | |||||
| } else { | |||||
| out.remark = `【${getJobType(record?.Cloudbrain?.JobType)}】【${record?.Cloudbrain?.ComputeResource}】【${record?.Cloudbrain?.ResourceSpec.FlavorInfo.desc}】`; | |||||
| } | |||||
| } | |||||
| } | |||||
| return out; | |||||
| }; | |||||
| @@ -0,0 +1,16 @@ | |||||
| import Vue from 'vue'; | |||||
| import ElementUI from 'element-ui'; | |||||
| import 'element-ui/lib/theme-chalk/index.css'; | |||||
| import localeEn from 'element-ui/lib/locale/lang/en'; | |||||
| import localeZh from 'element-ui/lib/locale/lang/zh-CN'; | |||||
| import { i18n, lang } from '~/langs'; | |||||
| import App from './vp-point.vue'; | |||||
| Vue.use(ElementUI, { | |||||
| locale: lang === 'zh-CN' ? localeZh : localeEn | |||||
| }); | |||||
| new Vue({ | |||||
| i18n, | |||||
| render: (h) => h(App), | |||||
| }).$mount('#__vue-root'); | |||||
| @@ -0,0 +1,308 @@ | |||||
| <template> | |||||
| <div class="__reward-pointer-c"> | |||||
| <div class="ui container" style="width:80%;min-width:1200px;"> | |||||
| <div class="__r_p_header"> | |||||
| <div> | |||||
| <p class="__title">{{ $t('calcPointDetails') }}</p> | |||||
| </div> | |||||
| <div style="padding: 0 5px; font-size: 14px"> | |||||
| <span> | |||||
| <i class="question circle icon link" style="color: rgba(3, 102, 214, 1)" data-position="right center" | |||||
| data-variation="mini"></i> | |||||
| <a href="/reward/point/rule" target="_blank" style="color: rgba(3, 102, 214, 1)">{{ | |||||
| $t('calcPointAcquisitionInstructions') | |||||
| }}</a> | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| <div class="__r_p_summary"> | |||||
| <div class="__r_p_summary_item-c __flex-1"> | |||||
| <div class="__val">{{ summaryInfo.available }}</div> | |||||
| <div class="__exp">{{ $t('CurrAvailableCalcPoints') }}</div> | |||||
| </div> | |||||
| <div class="__r_p_summary_line"></div> | |||||
| <div class="__r_p_summary_item-c __flex-1"> | |||||
| <div class="__val">{{ summaryInfo.gain }}</div> | |||||
| <div class="__exp">{{ $t('totalGainCalcPoints') }}</div> | |||||
| </div> | |||||
| <div class="__r_p_summary_item-c __flex-1"> | |||||
| <div class="__val">{{ summaryInfo.used }}</div> | |||||
| <div class="__exp">{{ $t('totalConsumeCalcPoints') }}</div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="__r_p_tab"> | |||||
| <div class="__r_p_tab-item" :class="tabIndex === 0 ? '__focus' : ''" style="border-radius: 5px 0px 0px 5px" | |||||
| @click="tabChange(0)"> | |||||
| {{ $t('gainDetail') }} | |||||
| </div> | |||||
| <div class="__r_p_tab-item" :class="tabIndex === 1 ? '__focus' : ''" style="border-radius: 0px 5px 5px 0px" | |||||
| @click="tabChange(1)"> | |||||
| {{ $t('consumeDetail') }} | |||||
| </div> | |||||
| </div> | |||||
| <div class="__r_p_table"> | |||||
| <div v-show="tabIndex === 0"> | |||||
| <el-table :data="tableData" row-key="sn" style="width: 100%" v-loading="loading" stripe | |||||
| v-if="tableData.length"> | |||||
| <el-table-column column-key="sn" prop="sn" :label="$t('serialNumber')" align="center" header-align="center" | |||||
| width="180"> | |||||
| </el-table-column> | |||||
| <el-table-column column-key="date" prop="date" :label="$t('time')" align="center" header-align="center" | |||||
| width="180"> | |||||
| </el-table-column> | |||||
| <el-table-column column-key="sourceType" prop="sourceType" :label="$t('scene')" align="center" | |||||
| header-align="center" width="180"></el-table-column> | |||||
| <el-table-column column-key="action" prop="action" :label="$t('behaviorOfPoint')" align="center" | |||||
| header-align="center" width="200"></el-table-column> | |||||
| <el-table-column column-key="remark" prop="remark" :label="$t('explanation')" align="left" min-width="200" | |||||
| header-align="center"> | |||||
| <template slot-scope="scope"> | |||||
| <span v-html="scope.row.remark"></span> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column column-key="amount" prop="amount" :label="$t('points')" align="center" | |||||
| header-align="center" width="120"></el-table-column> | |||||
| <template slot="empty"> | |||||
| <span>{{ loading ? $t('loading') : $t('noData') }}</span> | |||||
| </template> | |||||
| </el-table> | |||||
| <el-empty v-else :image-size="140" :description="$t('noPointGainRecord')"></el-empty> | |||||
| </div> | |||||
| <div v-show="tabIndex === 1"> | |||||
| <el-table :data="tableData" row-key="sn" style="width: 100%" v-loading="loading" stripe | |||||
| v-if="tableData.length"> | |||||
| <el-table-column column-key="sn" prop="sn" :label="$t('serialNumber')" align="center" header-align="center" | |||||
| width="180"> | |||||
| </el-table-column> | |||||
| <el-table-column column-key="date" prop="date" :label="$t('time')" align="center" header-align="center" | |||||
| width="180"> | |||||
| </el-table-column> | |||||
| <el-table-column column-key="status" prop="status" :label="$t('status')" align="center" | |||||
| header-align="center" width="120"> | |||||
| <template slot-scope="scope"> | |||||
| <span :style="{ color: scope.row.statusColor }">{{ scope.row.status }}</span> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column column-key="sourceType" prop="sourceType" :label="$t('scene')" align="center" | |||||
| header-align="center" width="180"></el-table-column> | |||||
| <el-table-column column-key="duration" prop="duration" :label="$t('runTime')" align="center" | |||||
| header-align="center" width="120"></el-table-column> | |||||
| <el-table-column column-key="remark" prop="remark" :label="$t('explanation')" align="left" min-width="200" | |||||
| header-align="center"> | |||||
| <template slot-scope="scope"> | |||||
| <span v-html="scope.row.remark"></span> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column column-key="taskName" prop="taskName" :label="$t('taskName')" align="center" | |||||
| header-align="center" width="180"> | |||||
| <template slot-scope="scope"> | |||||
| <span v-html="scope.row.taskName"></span> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column column-key="amount" prop="amount" :label="$t('points')" align="center" | |||||
| header-align="center" width="120"></el-table-column> | |||||
| <template slot="empty"> | |||||
| <span>{{ loading ? $t('loading') : $t('noData') }}</span> | |||||
| </template> | |||||
| </el-table> | |||||
| <el-empty v-else :image-size="140" :description="$t('noPointConsumeRecord')"></el-empty> | |||||
| </div> | |||||
| <div class="__r_p_pagination" v-if="tableData.length"> | |||||
| <div style="margin-top: 2rem"> | |||||
| <div class="center"> | |||||
| <el-pagination background @current-change="currentChange" :current-page="pageInfo.curpage" | |||||
| :page-sizes="pageInfo.pageSizes" :page-size="pageInfo.pageSize" | |||||
| layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total"> | |||||
| </el-pagination> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import { getPoint, getPointAccount, getPointList } from "~/apis/modules/point"; | |||||
| import { getRewardPointRecordInfo } from './utils'; | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| loading: false, | |||||
| summaryInfo: { | |||||
| available: 0, | |||||
| gain: 0, | |||||
| used: 0, | |||||
| }, | |||||
| tabIndex: 0, | |||||
| tableData: [], | |||||
| pageInfo: { | |||||
| curpage: 1, | |||||
| pageSize: 10, | |||||
| pageSizes: [10], | |||||
| total: 0, | |||||
| }, | |||||
| eventSource: null, | |||||
| }; | |||||
| }, | |||||
| components: {}, | |||||
| methods: { | |||||
| currentChange: function (val) { | |||||
| this.pageInfo.curpage = val; | |||||
| this.getTableData(); | |||||
| }, | |||||
| tabChange: function (index) { | |||||
| if (this.tabIndex === index) return; | |||||
| this.tabIndex = index; | |||||
| this.pageInfo.curpage = 1; | |||||
| this.pageInfo.total = 0; | |||||
| this.getTableData(); | |||||
| }, | |||||
| getSummaryInfo: function () { | |||||
| getPointAccount().then(res => { | |||||
| if (res.data && res.data.Code === 0) { | |||||
| const data = res.data.Data; | |||||
| this.summaryInfo.available = data.Balance; | |||||
| this.summaryInfo.gain = data.TotalEarned; | |||||
| this.summaryInfo.used = data.TotalConsumed; | |||||
| } | |||||
| }).catch(err => { | |||||
| console.log(err); | |||||
| }) | |||||
| }, | |||||
| getTableData: function () { | |||||
| this.loading = true; | |||||
| getPointList({ | |||||
| Operate: this.tabIndex === 0 ? 'INCREASE' : 'DECREASE', | |||||
| Page: this.pageInfo.curpage, | |||||
| // pageSize: this.pageInfo.pageSize, | |||||
| }).then((res) => { | |||||
| this.loading = false; | |||||
| const tableData = []; | |||||
| if (res.data && res.data.Code === 0) { | |||||
| const data = res.data.Data; | |||||
| const records = data.Records; | |||||
| for (let i = 0, iLen = records.length; i < iLen; i++) { | |||||
| const record = records[i]; | |||||
| tableData.push(getRewardPointRecordInfo(record)); | |||||
| } | |||||
| this.tableData.splice(0, Infinity, ...tableData); | |||||
| this.pageInfo.total = data.Total; | |||||
| } | |||||
| }) | |||||
| .catch((err) => { | |||||
| console.log(err); | |||||
| this.loading = false; | |||||
| this.tableData.splice(0, Infinity); | |||||
| }); | |||||
| }, | |||||
| }, | |||||
| mounted: function () { | |||||
| this.getSummaryInfo(); | |||||
| this.getTableData(); | |||||
| const { AppSubUrl, csrf, NotificationSettings } = window.config; | |||||
| if (NotificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource) { | |||||
| const source = new EventSource(`${AppSubUrl}/user/events`); | |||||
| source.addEventListener('reward-operation', (e) => { | |||||
| try { | |||||
| this.getSummaryInfo(); | |||||
| this.getTableData(); | |||||
| } catch (err) { | |||||
| console.error(err); | |||||
| } | |||||
| }); | |||||
| this.eventSource = source; | |||||
| } | |||||
| }, | |||||
| beforeDestroy: function () { | |||||
| this.eventSource && this.eventSource.close(); | |||||
| }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .__flex-1 { | |||||
| flex: 1; | |||||
| } | |||||
| .__reward-pointer-c { | |||||
| .__r_p_header { | |||||
| height: 30px; | |||||
| margin: 10px 0; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| .__title { | |||||
| font-weight: 400; | |||||
| font-size: 18px; | |||||
| color: rgb(16, 16, 16); | |||||
| line-height: 26px; | |||||
| } | |||||
| } | |||||
| .__r_p_summary { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 100px; | |||||
| background-color: rgb(245, 245, 246); | |||||
| .__r_p_summary_item-c { | |||||
| .__val { | |||||
| text-align: center; | |||||
| margin: 12px 0; | |||||
| font-weight: 400; | |||||
| font-size: 28px; | |||||
| color: rgb(16, 16, 16); | |||||
| } | |||||
| .__exp { | |||||
| text-align: center; | |||||
| font-weight: 400; | |||||
| font-size: 14px; | |||||
| color: rgba(54, 56, 64, 1); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .__r_p_summary_line { | |||||
| width: 1px; | |||||
| height: 80%; | |||||
| background-color: rgb(212, 212, 213); | |||||
| } | |||||
| .__r_p_tab { | |||||
| display: flex; | |||||
| margin: 18px 0; | |||||
| .__r_p_tab-item { | |||||
| width: 115px; | |||||
| height: 38px; | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| border: 1px solid rgb(225, 227, 230); | |||||
| color: #101010; | |||||
| box-sizing: border-box; | |||||
| cursor: pointer; | |||||
| &.__focus { | |||||
| border-color: rgb(50, 145, 248); | |||||
| color: rgb(50, 145, 248); | |||||
| cursor: default; | |||||
| } | |||||
| } | |||||
| } | |||||
| .__r_p_table { | |||||
| /deep/ .el-table__header { | |||||
| th { | |||||
| background: rgb(245, 245, 246); | |||||
| color: rgb(96, 98, 102); | |||||
| font-weight: 400; | |||||
| font-size: 14px; | |||||
| } | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -29,6 +29,11 @@ for (const path of stadalonePaths) { | |||||
| standalone[parse(path).name] = [path]; | standalone[parse(path).name] = [path]; | ||||
| } | } | ||||
| const vuePages = {}; | |||||
| for (const path of glob('web_src/vuepages/**/vp-*.js')) { | |||||
| vuePages[parse(path).name] = [path]; | |||||
| } | |||||
| const isProduction = process.env.NODE_ENV !== 'development'; | const isProduction = process.env.NODE_ENV !== 'development'; | ||||
| module.exports = { | module.exports = { | ||||
| @@ -44,6 +49,7 @@ module.exports = { | |||||
| icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'), | icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'), | ||||
| ...standalone, | ...standalone, | ||||
| ...themes, | ...themes, | ||||
| ...vuePages, | |||||
| }, | }, | ||||
| devtool: false, | devtool: false, | ||||
| output: { | output: { | ||||
| @@ -267,6 +273,7 @@ module.exports = { | |||||
| symlinks: false, | symlinks: false, | ||||
| alias: { | alias: { | ||||
| vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only | vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only | ||||
| '~': resolve(__dirname, 'web_src/vuepages'), | |||||
| }, | }, | ||||
| extensions: ['.tsx', '.ts', '.js'] | extensions: ['.tsx', '.ts', '.js'] | ||||
| }, | }, | ||||
| @@ -29,6 +29,11 @@ for (const path of stadalonePaths) { | |||||
| standalone[parse(path).name] = [path]; | standalone[parse(path).name] = [path]; | ||||
| } | } | ||||
| const vuePages = {}; | |||||
| for (const path of glob('web_src/vuepages/**/vp-*.js')) { | |||||
| vuePages[parse(path).name] = [path]; | |||||
| } | |||||
| const isProduction = process.env.NODE_ENV !== 'development'; | const isProduction = process.env.NODE_ENV !== 'development'; | ||||
| module.exports = { | module.exports = { | ||||
| @@ -44,6 +49,7 @@ module.exports = { | |||||
| icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'), | icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'), | ||||
| ...standalone, | ...standalone, | ||||
| ...themes, | ...themes, | ||||
| ...vuePages | |||||
| }, | }, | ||||
| devtool: false, | devtool: false, | ||||
| output: { | output: { | ||||
| @@ -267,6 +273,7 @@ module.exports = { | |||||
| symlinks: false, | symlinks: false, | ||||
| alias: { | alias: { | ||||
| vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only | vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only | ||||
| '~': resolve(__dirname, 'web_src/vuepages'), | |||||
| }, | }, | ||||
| extensions: ['.tsx', '.ts', '.js'] | extensions: ['.tsx', '.ts', '.js'] | ||||
| }, | }, | ||||