本篇通過完整示例介紹如何實現一對多關系表單的相應服務及視圖。
一、准備數據結構
示例所采用的數據結構為“物資需求”一對多“物資清單”,通過IDE的實體設計器如下所示:
1. 物資(DonateItem)
主鍵為Id(Guid)
2. 物資需求(Requirement)
主鍵為Id(Guid)
3. 物資清單(RequireItem)
主鍵為Req(Requirement)+Item(DonateItem)
添加實體成員時選擇類型EntityRef(一對一)或EntitySet(一對多)可設置相應的導航屬性
二、實現需求列表顯示功能
1. 新建RequirementService服務實現加載列表數據
using System;
using System.Threading.Tasks;
namespace dns.ServiceLogic
{
public class RequirementService
{
/// <summary>
/// 分頁加載需求記錄
/// </summary>
public async Task<object> Load(int pageIndex, int pageSize)
{
var q = new SqlQuery<Entities.Requirement>();
q.Skip(pageSize * pageIndex).Take(pageSize);
q.OrderByDesc(t => t.Time);
return await q.ToListAsync();
}
}
}
2. 新建RequireList視圖
2.1 模版
<div>
<el-button-group>
<el-button type="primary" icon="fas fa-plus-square fa-fw">新建</el-button>
<el-button type="primary" icon="fas fa-edit fa-fw">修改</el-button>
<el-button type="primary" icon="fas fa-trash fa-fw">刪除</el-button>
<el-button @click="load" type="primary" icon="fas fa-search fa-fw">刷新</el-button>
</el-button-group>
<br/><br/>
<el-table :data="items" v-loading="loading" border stripe highlight-current-row
readonly>
<el-table-column prop="Donee" label="需求方"></el-table-column>
<el-table-column prop="Time" label="時間"></el-table-column>
<el-table-column prop="Contact" label="聯系人" width="80"></el-table-column>
<el-table-column prop="Phone" label="電話"></el-table-column>
<el-table-column prop="Address" label="地址"></el-table-column>
<el-table-column prop="PostCode" label="郵編" width="80"></el-table-column>
</el-table>
<el-pagination background layout="prev, pager, next" :total="1000">
</el-pagination>
</div>
2.2 腳本
@Component
export default class RequireList extends Vue {
items = [] //需求列表
loading = false
pageIndex = 0
pageSize = 20
load() {
this.loading = true
dns.Services.RequirementService.Load(this.pageIndex, this.pageSize).then(res => {
this.$set(this, 'items', $runtime.parseEntity(res))
this.loading = false
}).catch(err => {
this.loading = false
this.$message.error('加載需求列表失敗: ' + err)
})
}
mounted() {
this.load()
}
}
系統函數$runtime.parseEntity()用於將調用服務的結果內的數據對象如{ID:xxx,Name:xxx}轉換為前端的Entity對象,另如果調用服務的結果只用作展示可以不用轉換。
2.3 預覽
點擊“Preview”預覽,當然當前沒有任何數據。
三、實現新建需求功能
3.1 修改RequirementService實現保存方法
/// <summary>
/// 保存需求
/// </summary>
public async Task Save(Entities.Requirement req)
{
//TODO:驗證
using var conn = await DataStore.Default.OpenConnectionAsync();
using var txn = conn.BeginTransaction();
//保存主記錄
await DataStore.Default.SaveAsync(req, txn);
//保存子記錄
foreach (var item in req.Items)
{
await DataStore.Default.SaveAsync(item, txn);
}
//處理已刪除的物資
if (req.PersistentState != PersistentState.Detached)
{
foreach (var item in req.Items.DeletedItems)
{
await DataStore.Default.DeleteAsync(item, txn);
}
}
//遞交事務
txn.Commit();
}
3.2 新建ItemService實現用於綁定的加載方法
using System;
using System.Threading.Tasks;
namespace dns.ServiceLogic
{
public class ItemService
{
/// <summary>
/// 加載用於前端選擇綁定
/// </summary>
public async Task<object> LoadForSelect()
{
var q = new SqlQuery<Entities.DonateItem>();
return await q.ToListAsync(t => new { t.Id, t.Name, t.Spec });
}
}
}
3.3 新建RequireView編輯視圖
3.3.1 模版
<div>
<!-- 表單頭 -->
<el-row :gutter="20" type="flex" align="middle">
<el-col :span="3">需求方:</el-col>
<el-col :span="9">
<el-input v-model="req.Donee"></el-input>
</el-col>
<el-col :span="3">時間:</el-col>
<el-col :span="9">
<el-date-picker v-model="req.Time" type="date" placeholder="選擇日期" style="width:100%">
</el-date-picker>
</el-col>
</el-row>
<br/>
<el-row :gutter="20" type="flex" align="middle">
<el-col :span="3">聯系人:</el-col>
<el-col :span="9">
<el-input v-model="req.Contact"></el-input>
</el-col>
<el-col :span="3">電話:</el-col>
<el-col :span="9">
<el-input v-model="req.Phone"></el-input>
</el-col>
</el-row>
<br/>
<el-row :gutter="20" type="flex" align="middle">
<el-col :span="3">地址:</el-col>
<el-col :span="9">
<el-input v-model="req.Address"></el-input>
</el-col>
<el-col :span="3">郵編:</el-col>
<el-col :span="9">
<el-input v-model="req.PostCode"></el-input>
</el-col>
</el-row>
<br/>
<!-- 物資列表 -->
<el-table :data="items" border highlight-current-row readonly>
<el-table-column type="index"></el-table-column>
<el-table-column label="物資" width="280px">
<template slot-scope="scope">
<el-select v-model="scope.row.ItemId" value-key="Id" style="width:100%" placeholder="請選擇">
<el-option v-for="item in optItems" :key="item.Id" :label="item.Name+' '+item.Spec"
:value="item.Id">
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="數量" width="130px">
<template slot-scope="scope">
<el-input-number v-model="scope.row.Quantity" controls-position="right" :min="1" style="width:100%">
</el-input-number>
</template>
</el-table-column>
<el-table-column label="備注">
<template slot-scope="scope">
<el-input v-model="scope.row.Comment"></el-input>
</template>
</el-table-column>
<el-table-column align="right" width="100px">
<template slot="header" slot-scope="scope">
<el-button @click="onAddItem" icon="fa fa-plus fa-fw" size="mini">添加</el-button>
</template>
<template slot-scope="scope">
<el-button @click="onDeleteItem(scope.$index)" icon="fa fa-times fa-fw" size="mini">刪除</el-button>
</template>
</el-table-column>
</el-table>
<br/>
<div style="text-align:center">
<el-button @click="onSave" type="primary" icon="fas fa-save fa-fw">保存</el-button>
</div>
</div>
3.3.2 腳本
@Component
export default class RequireView extends Vue {
@Prop({ type: Object, default: {} }) req: dns.Entities.Requirement
optItems = [] //物資選擇列表
/** 用於Table綁定,過濾已標為刪除的 */
get items() {
if (this.req.Items) {
return this.req.Items.filter(t => !t.isDeleted())
}
return null
}
/** 加載用於綁定下拉選擇的物資列表 */
loadItems() {
dns.Services.ItemService.LoadForSelect().then(res => {
this.$set(this, 'optItems', res)
}).catch(err => {
this.$message.error("加載物資列表失敗: " + err)
})
}
/** 添加物資 */
onAddItem() {
if (!this.req.Items) { //僅測試
this.$set(this.req, 'Items', [])
}
this.req.Items.push(new dns.Entities.RequireItem())
}
/** 刪除物資 */
onDeleteItem(index) {
//新建的直接刪除,舊的標為刪除
if (this.req.Items[index].isNew()) {
this.req.Items.splice(index, 1)
} else {
this.req.Items[index].markDeleted()
}
}
onSave() {
dns.Services.RequirementService.Save(this.req).then(res => {
this.req.acceptChanges()
this.$message.success("保存成功")
}).catch(err => {
this.$message.error("保存錯誤: " + err)
})
}
mounted() {
this.loadItems()
}
}
3.4 修改RequireList視圖,實現新建功能
3.4.1 模版
<el-button @click="onCreate" type="primary" icon="fas fa-plus-square fa-fw">新建</el-button>
<!-- 省略 -->
<el-dialog title="需求記錄" :visible.sync="dlgVisible" width="800px">
<require-view :req="current"></require-view>
</el-dialog>
<!-- 省略 -->
3.4.2 腳本
@Component({
components: { RequireView: dns.Views.RequireView }
})
export default class RequireList extends Vue {
//----省略----
dlgVisible = false
current = null
//----省略----
onCreate() {
this.current = new dns.Entities.Requirement()
this.dlgVisible = true
}
//----省略----
現在可以在預覽內嘗試添加些數據了!
四、實現需求列表Pop顯示物資清單
主列表使用懶加載方式加載子表數據。
4.1 修改RequirementService服務實現加載子表數據方法
/// <summary>
/// 加載需求物資列表
/// </summary>
public async Task<object> LoadItems(Guid reqId)
{
var q = new SqlQuery<Entities.RequireItem>();
q.Where(t => t.ReqId == reqId);
q.Include(t => t.Item.Name);
q.Include(t => t.Item.Spec);
return await q.ToListAsync();
}
4.2 修改RequireList視圖
4.2.1 模版
<!-- 省略 -->
<el-table-column label="需求物資" width="80">
<template slot-scope="scope">
<!-- 物資清單列表 -->
<el-popover @show="loadItems(scope.row)" trigger="hover" placement="left" width="400">
<el-row v-for="(item,index) in scope.row.Items" :gutter="20">
<el-col :span="2">{{index+1}}.</el-col>
<el-col :span="4">{{item.ItemName}}</el-col>
<el-col :span="14">{{item.ItemSpec}}</el-col>
<el-col :span="4">{{item.Quantity}}</el-col>
</el-row>
<div slot="reference">
<el-tag size="medium">物資清單</el-tag>
</div>
</el-popover>
</template>
</el-table-column>
<!-- 省略 -->
4.2.2 腳本
/** 加載需求物資清單 */
loadItems(row: dns.Entities.Requirement) {
if (row.Items) { return }
dns.Services.RequirementService.LoadItems(row.Id).then(res => {
this.$set(row, 'Items', $runtime.parseEntity(res))
}).catch(err => {
this.$message.error('加載物資列表失敗: ' + err)
})
}
4.2.3 預覽
五、實現修改與刪除功能
5.1 修改RequirementService服務實現刪除方法
public async Task Delete(Entities.Requirement req)
{
//TODO:判斷狀態,已發放物資的不能刪除
using var conn = await DataStore.Default.OpenConnectionAsync();
using var txn = conn.BeginTransaction();
//先刪除子表記錄
var deleteItems = new SqlDeleteCommand<Entities.RequireItem>();
deleteItems.Where(t => t.ReqId == req.Id);
await DataStore.Default.ExecCommandAsync(deleteItems, txn);
//再刪除主表記錄
await DataStore.Default.DeleteAsync(req, txn);
//遞交事務
txn.Commit();
}
5.2 修改RequireList視圖
5.2.1 模版
<!-- 省略 -->
<el-button @click="onEdit" type="primary" icon="fas fa-edit fa-fw">修改</el-button>
<el-button @click="onDelete" type="primary" icon="fas fa-trash fa-fw">刪除</el-button>
<!-- 省略 -->
<el-table :data="items" v-loading="loading" @current-change="onCurrentChanged" border stripe highlight-current-row
readonly>
5.2.2 腳本
//----省略----
export default class RequireList extends Vue {
//----省略----
currentRow = null
//----省略----
onCurrentChanged(row) {
this.currentRow = row
}
onEdit() {
if (!this.currentRow) {
this.$message.warning("請先選擇記錄")
return
}
this.loadItems(this.currentRow)
this.current = this.currentRow
this.dlgVisible = true
}
onDelete() {
if (!this.currentRow) {
this.$message.warning("請先選擇記錄")
return
}
this.$confirm('確認刪除選擇的記錄嗎?', '提示', {
confirmButtonText: '確定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
dns.Services.RequirementService.Delete(this.currentRow).then(res => {
this.$message.success("刪除成功");
}).catch(err => {
this.$message.error("刪除失敗: " + err)
})
}).catch(() => { })
}
//----省略----
5.2.3 預覽
六、本篇小結
作者上篇提到實現獨立的不依賴內置存儲的版本,本篇示例即是基於此版本,下一步重點是針對此版本的測試與Bug修復。另一邊碼代碼一邊碼文實屬不易,作者需要您的支持請您多多點贊推薦!