view

code
form.css
:host { display: flex; width: 100%; height:100%; border-left:1px solid #ccc; } .invalid-box { border: 1px solid #a94442; } .invalid-error-tip { color: #a94442; } .select-box { width:308px; height: 22px; } label { line-height:20px; } .note { color:#bbb; font-size:12px; } .small-input { width: 80%; height: 19px; line-height: 19px; } .small-input :-moz-placeholder, .small-input :-ms-input-placeholder, .small-input ::-webkit-input-placeholder { line-height: 10px; font-size: 10px; } .sm-textBox-wrapper { width: 80%; } .sm-textBox-wrapper [placeholder] { text-overflow: ellipsis; font-style: italic; } .sm-textBox-wrapper .azc-input { box-sizing: border-box; font-size: 12px; outline: 0; width: 100%; } .error-input { border-color: #e81123 !important; border-style: solid; border-width: 1px; } .edit-input-wrapper { display: inline-block; float: left; } .error-icon { height: 19px; width: 10px; line-height: 19px; } .tootip-balloon-w { width: 115px; } .fxc-grid2.fxc-grid-sorting a.fxc-sortable { height:35px; }
form.html
<panel-component [menuItems]="menuItems" headerTitle="创建部署" initWidth="400px" (closeEvent)="onClose()"> <form style="margin:25px; 90%;height: 100%;overflow-x: hidden;" #dpyForm="ngForm"> <div style="90%;padding:5px 3px; font-size:12px;"> <div style="padding-top:4px;"> <label>App ID:</label> <span>{{currApp.name}}</span> </div> </div> <div style="90%;padding:5px 3px; font-size:12px;"> <div style="padding-top:4px;"> <label>Package ID:</label> <span>{{currPkg.version}}</span> </div> </div> <div style="90%;padding:5px 3px; font-size:12px;"> <div class="azc-required-anchor" style="float:left;padding-top:4px;"> <svg xmlns="http://www.w3.org/2000/svg" class=" fxs-portal-svg" role="presentation" aria-hidden="true" viewBox="0 0 6 6" focusable="false" xmlns:NS1="" NS1:xmlns:svg="http://www.w3.org/2000/svg"> <g> <path class="msportalfx-svg-c22" d="M 3.543 2.352 l 2.08 -0.716 L 6 2.687 l -2.076 0.675 L 5.21 5.158 l -0.942 0.676 l -1.242 -1.867 l -1.264 1.867 l -0.97 -0.676 l 1.305 -1.796 L 0 2.687 L 0.38 1.63 l 2.058 0.743 V 0.233 h 1.105 v 2.119 Z" /> </g> </svg> </div> <div style="200px;float:left;padding-top:4px;"> <label>请选择级别</label> </div> <div style="90%"> <select (change)="onValid(null)" class="select-box" [(ngModel)]="currDpy.Level" required name="Level" #level="ngModel" [ngClass]="{'invalid-box': (level.invalid && (level.dirty || level.touched))||showLevelError}"> <option *ngFor="let level of lstLevel" [value]='level.Key'>{{level.Value}}</option> </select> <div *ngIf="(level.invalid && (level.dirty || level.touched))||showLevelError" class="invalid-error-tip"> 级别为必填字段 </div> </div> </div> <div style="90%;padding:5px 3px; font-size:12px;"> <div class="azc-required-anchor" style="float:left;padding-top:4px;"> <svg xmlns="http://www.w3.org/2000/svg" class=" fxs-portal-svg" role="presentation" aria-hidden="true" viewBox="0 0 6 6" focusable="false" xmlns:NS1="" NS1:xmlns:svg="http://www.w3.org/2000/svg"> <g> <path class="msportalfx-svg-c22" d="M 3.543 2.352 l 2.08 -0.716 L 6 2.687 l -2.076 0.675 L 5.21 5.158 l -0.942 0.676 l -1.242 -1.867 l -1.264 1.867 l -0.97 -0.676 l 1.305 -1.796 L 0 2.687 L 0.38 1.63 l 2.058 0.743 V 0.233 h 1.105 v 2.119 Z" /> </g> </svg> </div> <div style="200px;float:left;padding-top:4px;"> <label>端口号配置</label> </div> <div style="100%"> <!--列表信息--> <div class="ext-hubs-browse-grid fxc-base fxs-grid-focus fxc-grid-sorting fxc-grid-scrolling fxc-grid-resizing fxs-grid-selection fxc-grid-contextMenu fxc-grid-grouping fxc-grid2 azc-control fxc-grid-verticalScroll" style=" 100%;"> <div class="fxc-grid-container azc-br-muted"> <div class="fxc-grid-tableContainer azc-br-muted" style="padding-top: 42px;"> <div class="fxc-grid-tableScrollContainer azc-br-muted"> <table class="fxc-grid-tableHeader fxs-grid-multiselection" data-grid-activation="true"> <thead> <tr> <th class="fxc-grid-sorting-header fxc-grid-column-header " style=" 21%;"> <div class="fxc-grid-header-wrapper"> <a aria-sort="none" class="fxc-sortable fxc-none"> <span class="fxc-grid-headerlabel">序号</span> </a> <div class="fxc-grid-resizableColumn-handle"> <div class="fxc-grid-resizableColumn-handle-line azc-bg-muted"> </div> </div> </div> </th> <th class="fxc-grid-sorting-header fxc-grid-column-header "> <div class="fxc-grid-header-wrapper"> <a aria-sort="none" class="fxc-sortable fxc-none"> <span class="fxc-grid-headerlabel">Docker镜像</span> </a> <div class="fxc-grid-resizableColumn-handle"> <div class="fxc-grid-resizableColumn-handle-line azc-bg-muted"> </div> </div> </div> </th> <th class="fxc-grid-sorting-header fxc-grid-column-header "> <div class="fxc-grid-header-wrapper"> <a aria-sort="none" class="fxc-sortable fxc-none"> <span class="fxc-grid-headerlabel">部署应用</span> </a> <div class="fxc-grid-resizableColumn-handle"> <div class="fxc-grid-resizableColumn-handle-line azc-bg-muted"> </div> </div> </div> </th> </tr> </thead> </table> <div class="fxc-grid-tableContent" style="position: relative; overflow-x: hidden;" > <table class="fxc-grid-full fxs-grid-multiselection" data-grid-activation="true"> <tbody class="fxc-grid-groupdata "> <tr class="fxc-grid-row fxs-portal-focus fxs-portal-hover" *ngFor="let port of lstPorts;let i = index"> <td class="fxc-grid-cell azc-br-muted" style="10%"> <span class="fxc-grid-cellContent fxs-ellipsis"> <span class="msportalfx-gridcolumn-assetsvg-text">{{i}}</span> </span> </td> <td class="fxc-grid-cell azc-br-muted" style="12%;"> <span class="fxc-grid-cellContent fxs-ellipsis"> <span class="msportalfx-gridcolumn-assetsvg-text">{{port.docker}}</span> </span> </td> <td class="fxc-grid-cell azc-br-muted" style="20%;"> <div class="sm-textBox-wrapper" tabindex="-1"> <div class="edit-input-wrapper"> <input [(ngModel)]="port.app" (blur)="onValid(i)" (keyup)="onValid(i)" class="azc-input small-input" min="1" pattern="^[1-9]+[0-9]*$" maxlength="5" name="Ports" required type="number" placeholder="输入端口号" tabindex="0" [ngClass]="{'error-input': !port.valid}"> </div> <div *ngIf="!port.valid" class="fxc-base azc-control azc-dockedballoon azc-dockedballoon-validation azc-bg-default fxs-bg-error error-icon" (mouseenter)="toggleBalloonTip($event,true)" (mouseleave)="toggleBalloonTip($event,false)"> <div class="azc-dockedballoon-anchor"> <span> <svg height="100%" width="100%" aria-hidden="true" role="presentation" focusable="false"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#FxSymbol0-063"></use> </svg> </span> </div> </div> </div> </td> </tr> </tbody> </table> </div> </div> </div> </div> <div [ngClass]="{'azc-balloon-hidden':!isShowBalloon}" class="azc-dockedballoon-balloon azc-dockedballoon-validation azc-bg-default fxc-base azc-control azc-balloon azc-balloon-forcedisplayblock azc-balloon-position-alternate azc-balloon-box-top tootip-balloon-w" [ngStyle]="{'top.px': balloonTop,'left.px': balloonLeft}"> <div class="azc-br-muted-80-10 azc-balloon-pointer"></div> <div class="azc-bg-muted-80-10 fxs-text-white azc-balloon-content"><div class="azc-balloon-text">此字段为必填项且长度不超过5位的正整数</div></div> </div> </div> </div> </div> <div style="90%;padding:5px 3px; font-size:12px;"> <div class="azc-required-anchor" style="float:left;padding-top:4px;"> <svg xmlns="http://www.w3.org/2000/svg" class=" fxs-portal-svg" role="presentation" aria-hidden="true" viewBox="0 0 6 6" focusable="false" xmlns:NS1="" NS1:xmlns:svg="http://www.w3.org/2000/svg"> <g> <path class="msportalfx-svg-c22" d="M 3.543 2.352 l 2.08 -0.716 L 6 2.687 l -2.076 0.675 L 5.21 5.158 l -0.942 0.676 l -1.242 -1.867 l -1.264 1.867 l -0.97 -0.676 l 1.305 -1.796 L 0 2.687 L 0.38 1.63 l 2.058 0.743 V 0.233 h 1.105 v 2.119 Z" /> </g> </svg> </div> <div style="200px;float:left;padding-top:4px;"> <label>实例数</label> </div> <div style="90%"> <input type="number" min="1" pattern="^[1-9]+[0-9]*$" maxlength="5" (keyup)="onValid(null)" class="azc-input" style="305px;" [(ngModel)]="currDpy.InstanceCount" name="InstanceCount" [ngClass]="{'invalid-box': (instanceCount.invalid && (instanceCount.dirty || instanceCount.touched))||showInsCountError}" required #instanceCount="ngModel" /> <div *ngIf="(instanceCount.invalid && (instanceCount.dirty || instanceCount.touched))|| showInsCountError" class="invalid-error-tip"> 实例数为必填字段且为有效数字 </div> </div> </div> <div style="90%;padding:5px 3px; font-size:12px;"> <div style="100%;float:left;padding-top:4px;"> <label>描述</label> <span class="note">(注:多个描述项之间请用英文分号“;”分隔)</span> </div> <div style="90%"> <textarea class="azc-input" style="305px;height:100px" [(ngModel)]="currDpy.Description" name="Description"></textarea> </div> </div> </form> </panel-component> <router-outlet></router-outlet>
form.ts
import { Component, ViewChild} from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { AppStoreService } from '../service/appStoreService';
import { CommonService } from '../../providers/commonService';
@Component({
selector: 'deploy-page',
templateUrl: './deploy.html',
styleUrls: ['./deploy.css']
})
export class DeployPage {
@ViewChild('dpyForm') dpyForm;
constructor(
private router: Router,
private actRouter: ActivatedRoute,
private appStoreService: AppStoreService,
private comService: CommonService) {
}
currDpy: any = {
AppId: "",
PackageId:"",
Level:"",
Description: "",
InstanceCount: "",
Ports:""
};
id: string;
pkgId: any;
currApp: any = {
id: '',
name:''
};
currPkg: any = {
id: '',
version:''
};
lstPorts: any = [];
lstLevel: {} = [
{
"Key": 0,
"Value": "高"
},
{
"Key": 1,
"Value": "中"
},
{
"Key": 2,
"Value": "低"
}
];
showLevelError: boolean;
showInsCountError: boolean;
isShowBalloon: boolean = false;
balloonTop: any;
balloonLeft: any;
parentUrl: string;
menuItems: any = [
{ title: "提交", icon: "#FxSymbol0-001", event: this.onSaveDpyInfo.bind(this) }
]
ngOnInit(): void {
this.actRouter.params.subscribe((params: Params) => {
this.id = params["id"];
this.pkgId = params["pkgId"];
});
this.appStoreService.GetPkgOne(this.pkgId, (rtv) => {
this.currPkg = rtv;
rtv.ports.split(',').forEach(p => {
this.lstPorts.push({ 'docker':p,'app':'','valid':true});
});
});
this.appStoreService.GetAppOne(this.id, (rtv) => {
this.currApp = rtv;
});
this.parentUrl = "/webAppStore/" + this.id + "/version";
}
onClose() {
this.router.navigate([this.parentUrl, { id: this.id, pkgId: this.pkgId }]);
}
onValid(index: any) {
this.showLevelError = this.currDpy.Level ? false : true;
if (this.currDpy.InstanceCount && /^[1-9][0-9]{0,4}$/.test(this.currDpy.InstanceCount)) {
this.showInsCountError = false;
} else {
this.showInsCountError = true;
}
if (index && this.lstPorts[index]) {
this.validPort(this.lstPorts[index])
} else {
this.lstPorts.map(p => this.validPort(p))
}
}
validPort(port: any) {
port.app && /^[1-9][0-9]{0,4}$/.test(port.app) ? port.valid = true : port.valid = false;
}
onSaveDpyInfo() {
this.onValid(null);
let emptyItem = this.lstPorts.find(item => { return !item.app || item.valid == false });
if (this.dpyForm.form.valid && !emptyItem && this.showLevelError == false && this.showInsCountError == false) {
this.currDpy.AppId = this.currApp.id;
this.currDpy.PackageId = this.currPkg.id;
this.lstPorts.forEach(p => delete p.valid);
this.currDpy.Ports = JSON.stringify(this.lstPorts);
this.appStoreService.SaveAppDpyInfo(this.currDpy, () => {
var notifyBody = { action: 'refreshWebDpy', pkgId: this.pkgId};
this.comService.notifyOther(notifyBody);
this.router.navigate([this.parentUrl, { id: this.id, pkgId: this.pkgId }]);
});
}
}
toggleBalloonTip(event: any, isShow: boolean) {
this.isShowBalloon = isShow;
if (event) {
this.balloonLeft = event.pageX - 110;
this.balloonTop = event.pageY - 100;
event.stopPropagation();
}
}
}