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(); } } }