1. 程式人生 > >AppBox實戰: 如何實現一對多表單的增刪改查

AppBox實戰: 如何實現一對多表單的增刪改查

  本篇通過完整示例介紹如何實現一對多關係表單的相應服務及檢視。

一、準備資料結構

  示例所採用的資料結構為“物資需求”一對多“物資清單”,通過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修復。另一邊碼程式碼一邊碼文實屬不易,作者需要您的支援請您多多點贊推薦