316 lines
11 KiB
Vue
316 lines
11 KiB
Vue
<!-- setup 无法设置组件名称,组件名称keepAlive必须 -->
|
||
<script lang="ts">
|
||
export default {
|
||
name: "seata"
|
||
};
|
||
</script>
|
||
|
||
<script setup lang="ts">
|
||
import { reactive, onMounted, toRefs } from 'vue';
|
||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||
import {
|
||
Money,
|
||
Refresh,
|
||
RefreshLeft,
|
||
Right,
|
||
CircleCheckFilled,
|
||
CircleCloseFilled
|
||
} from "@element-plus/icons-vue";
|
||
import {
|
||
payOrder,
|
||
getSeataData,
|
||
resetSeataData
|
||
} from "@/api/lab/seata";
|
||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||
import { SeataFormData } from "@/types";
|
||
|
||
const state = reactive({
|
||
// 保留改变前数据
|
||
cacheSeataData: {
|
||
status: undefined,
|
||
stockNum: undefined,
|
||
balance: undefined
|
||
},
|
||
seataData: {
|
||
orderInfo: {
|
||
orderSn: undefined,
|
||
status: undefined
|
||
},
|
||
skuInfo: {
|
||
name: undefined,
|
||
picUrl: undefined,
|
||
stockNum: undefined
|
||
},
|
||
memberInfo: {
|
||
nickName: undefined,
|
||
avatarUrl: undefined,
|
||
balance: undefined
|
||
}
|
||
},
|
||
|
||
loading: false,
|
||
|
||
submitData: {
|
||
openTx: true, // 是否开启事务
|
||
orderEx: true // 订单修改异常
|
||
} as SeataFormData
|
||
})
|
||
|
||
const { cacheSeataData, seataData, loading, submitData } = toRefs(state)
|
||
|
||
/**
|
||
* 订单支付(模拟)
|
||
*/
|
||
function handleOrderPay() {
|
||
|
||
// 数据校验
|
||
if (seataData.value.skuInfo.stockNum && seataData.value.skuInfo.stockNum != 999
|
||
|| (seataData.value.memberInfo.balance && seataData.value.memberInfo.balance != 1000000000)
|
||
|| (seataData.value.orderInfo.status && seataData.value.orderInfo.status != 101)
|
||
) {
|
||
ElMessageBox.confirm(
|
||
'检查到当前数据已被污染,请先重置数据后尝试提交?',
|
||
'警告',
|
||
{
|
||
confirmButtonText: '重置数据',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
handleDataReset()
|
||
}).catch(() => {
|
||
})
|
||
} else {
|
||
// 订单支付模拟提交
|
||
|
||
loading.value = true
|
||
payOrder(submitData.value).then(() => {
|
||
ElMessage.success('订单支付成功')
|
||
}).finally(() => {
|
||
cacheSeataData.value = {
|
||
status: seataData.value.orderInfo.status,
|
||
stockNum: seataData.value.skuInfo.stockNum,
|
||
balance: seataData.value.memberInfo.balance
|
||
}
|
||
loadData();
|
||
})
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载数据
|
||
*/
|
||
function loadData() {
|
||
loading.value = true
|
||
getSeataData().then((response: any) => {
|
||
seataData.value = response.data
|
||
loading.value = false
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 刷新数据
|
||
*/
|
||
function handleDataRefresh() {
|
||
loading.value = true
|
||
loadData();
|
||
}
|
||
|
||
/**
|
||
* 数据重置
|
||
*/
|
||
function handleDataReset() {
|
||
loading.value = true
|
||
resetSeataData().then(() => {
|
||
ElMessage.success('数据还原成功')
|
||
loading.value = false;
|
||
cacheSeataData.value = {
|
||
status: undefined,
|
||
stockNum: undefined,
|
||
balance: undefined
|
||
}
|
||
loadData();
|
||
})
|
||
}
|
||
|
||
onMounted(() => {
|
||
// 第一次加载重置数据测试
|
||
handleDataReset();
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="app-container">
|
||
<el-alert type="info">
|
||
<p style="font-size: 16px;">
|
||
<b>模拟订单支付流程:</b>
|
||
扣减商品库存 → 扣减会员余额 → 修改订单状态
|
||
</p>
|
||
<p style="font-size: 14px;">
|
||
<b> 分布式事务生效判断:</b>
|
||
<el-link :icon="CircleCheckFilled" type="success">全部成功</el-link>
|
||
或
|
||
<el-link type="danger" :icon="CircleCloseFilled">全部失败</el-link>
|
||
</p>
|
||
|
||
<p style="font-size: 14px;">
|
||
<b> 博客教程:</b>
|
||
<el-link type="primary" href="https://www.cnblogs.com/haoxianrui/" target="_blank">
|
||
https://www.cnblogs.com/haoxianrui</el-link>
|
||
</p>
|
||
</el-alert>
|
||
|
||
<el-card class="box-card" shadow="always" style="margin-top:20px;">
|
||
<el-form :inline="true">
|
||
<el-row>
|
||
<el-col :span="20">
|
||
<el-form-item>
|
||
<el-switch v-model="submitData.openTx" active-value="" active-text="开启事务"
|
||
inactive-text="关闭事务" />
|
||
</el-form-item>
|
||
|
||
<el-form-item>
|
||
<el-button type="primary" :icon="Money" @click="handleOrderPay">订单支付</el-button>
|
||
<el-button :icon="Refresh" @click="handleDataRefresh">刷新数据</el-button>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="4" style="text-align: right;">
|
||
<el-button :icon="RefreshLeft" @click="handleDataReset">重置数据</el-button>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<el-row :gutter="10" style="margin-top:20px;" v-loading="loading">
|
||
<el-col :span="8" :xs="24" class="card-panel__col">
|
||
<el-card class="box-card" shadow="always">
|
||
<template #header>
|
||
<svg-icon icon-class="goods" />
|
||
商品信息
|
||
</template>
|
||
|
||
<div style="display: flex;">
|
||
<el-image style="width: 100px; height: 100px" :src="seataData.skuInfo.picUrl" fit="fill" />
|
||
<div style="margin-left: 10px;">
|
||
<el-form-item label="商品名称:">
|
||
{{ seataData.skuInfo.name }}
|
||
</el-form-item>
|
||
<el-form-item label="库存数量:" style="display: flex;">
|
||
<div v-if="cacheSeataData.stockNum != null">
|
||
{{ cacheSeataData.stockNum }} 部
|
||
<el-icon>
|
||
<right />
|
||
</el-icon>
|
||
</div>
|
||
|
||
{{ seataData.skuInfo.stockNum }} 部
|
||
|
||
<div v-if="cacheSeataData.stockNum" style="margin-left: 50px;">
|
||
<el-link v-if="cacheSeataData.stockNum != seataData.skuInfo.stockNum" type="success"
|
||
:underline="false" :icon="CircleCheckFilled">
|
||
修改成功
|
||
</el-link>
|
||
<el-link v-else-if="cacheSeataData.stockNum == seataData.skuInfo.stockNum"
|
||
type="danger" :underline="false" :icon="CircleCloseFilled">
|
||
修改失败
|
||
</el-link>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
|
||
<el-col :span="8" :xs="24" class="card-panel__col">
|
||
<el-card class="box-card" shadow="always">
|
||
<template #header>
|
||
<svg-icon icon-class="user" />
|
||
会员信息
|
||
</template>
|
||
|
||
<div style="display: flex;">
|
||
<el-image style="width: 100px; height: 100px" :src="seataData.memberInfo.avatarUrl"
|
||
fit="fill" />
|
||
<div style="margin-left: 10px;">
|
||
<el-form-item label="会员昵称:">
|
||
{{ seataData.memberInfo.nickName }}
|
||
</el-form-item>
|
||
<el-form-item label="会员余额:">
|
||
<div v-if="cacheSeataData.balance != null">
|
||
{{ (cacheSeataData.balance as any) / 100 }} 元
|
||
<el-icon>
|
||
<right />
|
||
</el-icon>
|
||
</div>
|
||
{{ (seataData.memberInfo.balance as any) / 100 }} 元
|
||
|
||
<div v-if="cacheSeataData.balance" style="margin-left: 50px;">
|
||
<el-link v-if="cacheSeataData.balance != seataData.memberInfo.balance"
|
||
type="success" :underline="false" :icon="CircleCheckFilled">
|
||
修改成功
|
||
</el-link>
|
||
<el-link v-else-if="cacheSeataData.balance == seataData.memberInfo.balance"
|
||
type="danger" :underline="false" :icon="CircleCloseFilled">
|
||
修改失败
|
||
</el-link>
|
||
</div>
|
||
</el-form-item>
|
||
</div>
|
||
</div>
|
||
|
||
</el-card>
|
||
</el-col>
|
||
|
||
<el-col :span="8" :xs="24" class="card-panel__col">
|
||
<el-card class="box-card" shadow="always">
|
||
<template #header>
|
||
<svg-icon icon-class="order" />
|
||
订单信息
|
||
<el-checkbox v-model="submitData.orderEx" :label="true" style="float: right;color: #F56C6C;">
|
||
搞点异常</el-checkbox>
|
||
</template>
|
||
<el-form-item label="订单编号:">
|
||
{{ seataData.orderInfo.orderSn }}
|
||
</el-form-item>
|
||
|
||
<el-form-item label="订单状态:">
|
||
<div v-if="cacheSeataData.status == 101">
|
||
<el-tag type="info">
|
||
待支付
|
||
</el-tag>
|
||
<el-icon>
|
||
<right />
|
||
</el-icon>
|
||
</div>
|
||
<el-tag v-if="seataData.orderInfo.status == 101" type="info">待支付</el-tag>
|
||
<el-tag v-else-if="seataData.orderInfo.status == 201" type="success">已支付</el-tag>
|
||
|
||
<div v-if="cacheSeataData.balance" style="margin-left: 50px;">
|
||
<el-link v-if="cacheSeataData.status != seataData.orderInfo.status" type="success"
|
||
:underline="false" :icon="CircleCheckFilled">
|
||
修改成功
|
||
</el-link>
|
||
<el-link v-else type="danger" :underline="false" :icon="CircleCloseFilled">
|
||
修改失败
|
||
</el-link>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
</el-card>
|
||
</el-col>
|
||
|
||
</el-row>
|
||
</div>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
.card-panel__col {
|
||
margin-bottom: 12px;
|
||
|
||
.el-link {
|
||
font-size: 16px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
}
|
||
</style> |