当前位置: 首页 > news >正文

中州养老Day02

护理项目前端页面编写

  • UI

当点击新增护理项目按钮或者是列表项中编辑的时候,需要弹窗进行新增或者是编辑,如下图

  • 点击【新增护理项目】,出【新增护理项目】弹窗
  • 点击编辑,出【编辑护理项目】弹窗

当编辑输入框内容的时候,验证规则如下:

关于价格、排序、图片这三个字段,需要进一步查看公共说明文档

在列表项操作中的删除禁用也会有弹窗,大家可以打开原型的全局/公共说明

删除弹窗

禁用弹窗

注意:启用与禁用操作、逻辑相反,且不出确认弹窗;

通过以上分析,我们现在大概知道了,这个护理项目分为了四个部分,分别是:

  • 列表页
  • 新增和编辑弹窗
  • 删除弹窗
  • 禁用弹窗

TDesign组件官方文档: TDesign Web Vue Next

当我们已经完成了需求分析,开发具体功能基本的步骤有四个

  • 先到TDesign组件找到对应的组件,先用静态组件在项目中展示效果
  • 编写接口代码,参考knife4j在线接口文档
  • 修改静态页面,调用接口,动态渲染数据
  • 样式微调,公共组件封装

疑问:没有在线接口文档怎么办?

  • 先到TDesign组件找到对应的组件,先用静态组件在项目中展示效果
  • mock接口数据(模拟接口和数据) | 离线的接口文档(前后端共同制定)
  • 修改静态页面,调用mock接口,渲染数据
  • 样式微调,公共组件封装

两者对比:在后期接口联调的时候,mock接口的成本更高

页面分条件查询

要参考表格美化: TDesign Web Vue Next

  • 找到基础表格,到我们创建的index.vue里面
<template><t-space direction="vertical"><!-- 按钮操作区域 --><t-radio-group v-model="size" variant="default-filled"><t-radio-button value="small">小尺寸</t-radio-button><t-radio-button value="medium">中尺寸</t-radio-button><t-radio-button value="large">大尺寸</t-radio-button></t-radio-group><t-space><t-checkbox v-model="stripe"> 显示斑马纹 </t-checkbox><t-checkbox v-model="bordered"> 显示表格边框 </t-checkbox><t-checkbox v-model="hover"> 显示悬浮效果 </t-checkbox><t-checkbox v-model="tableLayout"> 宽度自适应 </t-checkbox><t-checkbox v-model="showHeader"> 显示表头 </t-checkbox></t-space><!-- 当数据为空需要占位时,会显示 cellEmptyContent --><t-tablerow-key="index":data="data":columns="columns":stripe="stripe":bordered="bordered":hover="hover":table-layout="tableLayout ? 'auto' : 'fixed'":size="size":pagination="pagination":show-header="showHeader"cell-empty-content="-"resizablelazy-load@row-click="handleRowClick"></t-table></t-space>
</template><script setup lang="jsx">
import { ref } from 'vue';
import { ErrorCircleFilledIcon, CheckCircleFilledIcon, CloseCircleFilledIcon } from 'tdesign-icons-vue-next';const statusNameListMap = {0: { label: '审批通过', theme: 'success', icon: <CheckCircleFilledIcon /> },1: { label: '审批失败', theme: 'danger', icon: <CloseCircleFilledIcon /> },2: { label: '审批过期', theme: 'warning', icon: <ErrorCircleFilledIcon /> },
};
const data = [];
const total = 28;
for (let i = 0; i < total; i++) {data.push({index: i + 1,applicant: ['贾明', '张三', '王芳'][i % 3],status: i % 3,channel: ['电子签署', '纸质签署', '纸质签署'][i % 3],detail: {email: ['w.cezkdudy@lhll.au', 'r.nmgw@peurezgn.sl', 'p.cumx@rampblpa.ru'][i % 3],},matters: ['宣传物料制作费用', 'algolia 服务报销', '相关周边制作费', '激励奖品快递费'][i % 4],time: [2, 3, 1, 4][i % 4],createTime: ['2022-01-01', '2022-02-01', '2022-03-01', '2022-04-01', '2022-05-01'][i % 4],});
}const stripe = ref(true);
const bordered = ref(true);
const hover = ref(false);
const tableLayout = ref(false);
const size = ref('medium');
const showHeader = ref(true);const columns = ref([{ colKey: 'applicant', title: '申请人', width: '100' },{colKey: 'status',title: '申请状态',cell: (h, { row }) => {return (<t-tag shape="round" theme={statusNameListMap[row.status].theme} variant="light-outline">{statusNameListMap[row.status].icon}{statusNameListMap[row.status].label}</t-tag>);},},{ colKey: 'channel', title: '签署方式' },{ colKey: 'detail.email', title: '邮箱地址', ellipsis: true },{ colKey: 'createTime', title: '申请时间' },
]);const handleRowClick = (e) => {console.log(e);
};const pagination = {defaultCurrent: 1,defaultPageSize: 5,total,
};
</script>
  • 创建constants.ts文件,加入以下代码
export const COLUMNS = [{title: '序号',align: 'left',width: 100,minWidth: 100,colKey: 'rowIndex'},{ title: '护理图片', width: 150, minWidth: '150px', colKey: 'image' },{title: '护理项目名称',minWidth: '150px',colKey: 'name'},{title: '价格(元)',minWidth: '160px',colKey: 'price'},{title: '单位',minWidth: '150px',colKey: 'unit'},{title: '排序',minWidth: '150px',colKey: 'orderNo'},{title: '创建人',minWidth: '200px',colKey: 'creator'},{title: '创建时间',minWidth: '180px',colKey: 'createTime'},{title: '状态',colKey: 'status',width: 120,minWidth: '120px',cell: (h, { row }) => {const statusList = {1: {label: '启用'},0: {label: '禁用'}}return h('span',{class: `status-dot status-dot-${row.status}`},statusList[row.status].label)}},{align: 'left',fixed: 'right',width: 154,minWidth: '154px',colKey: 'op',title: '操作'}
]
  • 更改index.vue

  • 添加样式- 图片样式

目前在列表中展示的是图片的路径,我们的需求是,需要展示小图,并且可以预览图片(大图)

这个在TDesign组件中已经提供了

网址:TDesign Web Vue Next

<t-table><!-- 图片预览及展示 --><template #image="{ row }"><div class="tdesign-demo-image-viewer__base"><t-image-viewer :images="[row.image]"><template #trigger="{ open }"><div class="tdesign-demo-image-viewer__ui-image"><imgalt="test":src="row.image"class="tdesign-demo-image-viewer__ui-image--img"/><divclass="tdesign-demo-image-viewer__ui-image--hover"@click="open"><span><BrowseIcon size="1.4em" /> 预览</span></div></div></template></t-image-viewer></div></template>
</t-table>

  • 小数点展示
<!--处理价格展示--><template #price="{row}">{{isDecimals(row.price)?row.price:row.price+'.00'}}</template>//判断数据是否包含小数点
const isDecimals =(val)=>{if (String(val).includes('.')>-1) {return true;}return false;
}

  • 按钮处理
<!--按钮处理 --><template #op="{row}"><div class="operateCon"><a class="btn-dl">删除</a><a class="font-bt">编辑</a><a class="delete">禁用</a></div></template>

  • 添加分页查询
 <!-- 分页 --><t-paginationv-if="total > 10"v-model="pagination.pageNum"v-model:pageSize="pagination.pageSize":total="total"@change="onPageChange"/>
//分页对象
const pagination = ref({pageSize: 10,pageNum: 1
})//生命周期
onMounted(() => {getList()
})//获取列表数据
const getList = async () => {const res = await getProjectList(pagination.value)data.value = res.data.recordstotal.value = Number(res.data.total)
}// 翻页设置当前页
const onPageChange = (val) => {pagination.value.pageNum = val.currentpagination.value.pageSize = val.pageSizegetList()
}

  • 序号处理

我们发现,在上述的效果展示中,没有序号了

因为我们之前在使用for循环模拟数据的时候是给了设置了一个index字段的,但是我们查询的接口中并没有这个字段,现在我们可以使用前端TDesign中的表格属性rowIndex解决,代码如下:

我们同样在<t-table></t-table>标签对内处理字段的展示,代码如下:

<template><t-table><!-- 序号 --><template #rowIndex="{ rowIndex }">{{ rowIndex + 1 }}</template></t-table>
</template>
  • 全部代码
<template><div class="min-h serveProject bg-wt"><div class="baseList"><div class="tableBoxs"><!-- 当数据为空需要占位时,会显示 cellEmptyContent --><t-table :row-key="rowKey" :data="data" :columns="COLUMNS" vertical-align="middle":hover="hover":loading="dataLoading"tabel-content-width="100%"table-layout="fixed"><!-- 序号 --><template #rowIndex="{ rowIndex }">{{ rowIndex + 1 }}</template><!-- 图片预览 --><template #image="{ row }"><div><div class="tdesign-demo-image-viewer__base"><t-image-viewer :images="[row.image]"><template #trigger="{ open }"><div class="tdesign-demo-image-viewer__ui-image"><img alt="test" :src="row.image" class="tdesign-demo-image-viewer__ui-image--img" /><div class="tdesign-demo-image-viewer__ui-image--hover" @click="open"><span><BrowseIcon size="1.4em" /> 预览</span></div></div></template></t-image-viewer></div></div></template><!-- 价格拼接 --><template #price="{ row }">{{ isDecimals(row.price) ? row.price : row.price + '.00' }}</template><!-- 按钮处理 --><template #op="{ row }"><div class="operateCon"><a class="btn-dl">删除</a><a class="font-bt">编辑</a><a class="delete">禁用</a></div></template></t-table><!-- 分页 --><t-paginationv-if="total > 10"v-model="pagination.pageNum"v-model:pageSize="pagination.pageSize":total="total"@change="onPageChange"/></div></div></div>
</template><script setup lang="jsx">
import { ref,onMounted } from 'vue';
import { COLUMNS } from './constants'
import { getProjectList } from '@/api/serve'const data = ref([]);
const total = ref(0);
const dataLoading = ref(false) // 加载中const pagination = ref({pageSize: 10,pageNum: 1
})
//生命周期
onMounted(() => {getList()
})//调用接口
const getList = async () => {const res = await getProjectList(pagination.value)data.value = res.data.recordstotal.value = Number(res.data.total)
}// 翻页设置当前页
const onPageChange = (val) => {pagination.value.pageNum = val.currentpagination.value.pageSize = val.pageSizegetList()
}//判断当前参数是否包含小数点
const isDecimals = (val) => {if (String(val).indexOf('.') > -1) {return true;}return false;
}</script>

抽取组件

我们完成了列表查询以后,发现index.vue中已经有了不少的代码了,后面我还有搜索表单、新增、编辑、删除、禁用等功能,如果所有的内容都放在同一个vue中不太好,原因有两个,第一不太好阅读,后期修改调试不方便;第二不通用,假如其他页面有相同的功能,不能复用

所以通常情况下,我们都会对一个组件进行封装,封装为一个单独的vue,然后让index.vue去引用

抽取组件

我们在pages/serve/plan/project目录中新增一个目录components,新增一个TableList.vue组件

我们可以把index.vue中的代码全粘贴过来进行改造,其中调用接口、接口的参数、具体的方法还是在父组件中执行

  • 如果子组件需要让父组件传递属性,需要在子组件中定义defineProps并需要指明类型
  • 如果子组件需要调用父组件的方法,需要在子组件中定义defineEmits需要指定方法列表
  • 如果子组件需要监听父组件的参数变化,则需要使用watch来监听
<template><div class="baseList"><div class="tableBoxs"><!-- 当数据为空需要占位时,会显示 cellEmptyContent --><t-table:data="data":columns="COLUMNS":row-key="rowKey"vertical-align="middle":hover="true":loading="dataLoading"table-layout="fixed"table-content-width="100%"><!-- 处理序号 --><template #rowIndex="{ rowIndex }">{{ rowIndex + 1 }}</template><!-- 图片预览及展示 --><template #image="{ row }"><div class="tdesign-demo-image-viewer__base"><t-image-viewer :images="[row.image]"><template #trigger="{ open }"><div class="tdesign-demo-image-viewer__ui-image"><img alt="test" :src="row.image" class="tdesign-demo-image-viewer__ui-image--img" /><div class="tdesign-demo-image-viewer__ui-image--hover" @click="open"><span><BrowseIcon size="1.4em" /> 预览</span></div></div></template></t-image-viewer></div></template><!-- 价格拼接 --><template #price="{ row }">{{ isDecimals(row.price) ? row.price : row.price + '.00' }}</template><!-- 操作栏 --><template #op="{ row }"><div class="operateCon"><a class="btn-dl">删除</a><a class="font-bt">编辑</a><a class="delete">禁用</a></div></template></t-table><t-pagination v-if="total > 10" :total="total" v-model:current="pagination.pageNum"v-model:pageSize="pagination.pageSize" @change="onPageChange" /></div></div>
</template>
<script setup lang="ts">import { COLUMNS } from '../constants'// 行的key
const rowKey = 'index'const props = defineProps({data: {type: Object,default: () => {return {}}},// 总条数total: {type: Number,default: 0},pagination: {type: Object,default: () => {return {}}},// 加载状态dataLoading: {type: Boolean,default: false}
})//声明方法
const emit = defineEmits(['onPageChange'
])//点击翻页
const onPageChange = (val) => {emit('onPageChange', val)
}const isDecimals = (val) => {if (String(val).indexOf('.') > -1) {return true}return false
}</script>

修改index.vue,删除之前的<t-table>标签的内容,与当前标签相关的配置也可以删除(TabalList中已定义)

在index.vue中引入新创建的组件

import TableList  from './components/TableList.vue'

<template></template>标签定义TableList组件

<template><div class="min-h serveProject bg-wt"><TableList :data="data":total="total":pagination="pagination":dataLoading="dataLoading"@onPageChange="onPageChange"></TableList></div></template>

参数和方法的传递

:data、:pagination、:total这三个就是TableList组件需要的变量,通过这三个属性传递

@getCurrent、@isDecimals这两个就是TableList组件需要的方法

  • index.vue最终代码
<template><div class="min-h serveProject bg-wt"><TableList :data="data":total="total":pagination="pagination":dataLoading="dataLoading"@onPageChange="onPageChange"></TableList></div>
</template><script setup lang="jsx">import { ref,onMounted } from 'vue';
import { getProjectList} from '@/api/serve'
import TableList from './components/TableList.vue'const data = ref([]);
const total = ref(0);
const dataLoading = ref(false) // 加载中const pagination = ref({pageNum: 1,pageSize: 10
})//初始完成后执行查询方法
onMounted(()=>{getList();
})//翻页设置当前页
const onPageChange =(val) =>{pagination.value.pageNum = val.currentpagination.value.pageSize = val.pageSizegetList()
}//调用接口方法
const getList = async () =>{const res = await getProjectList(pagination.value);data.value = res.data.records;total.value = Number(res.data.total)
}</script>

搜索栏开发

在pages/serve/plan/project/components路径新建SearchFrom.vue

<template><div class="formBox"><t-form ref="form" :model="searchData" label-width="98"><t-row><t-col><t-form-item label="护理项目名称:" name="name"><t-input placeholder="请输入内容" v-model="searchData.name" class="form-item-content" type="search"clearable @clear="handleClear('name')" /></t-form-item></t-col><t-col><t-form-item label="状态:" name="status"><t-select clearable v-model="searchData.status" placeholder="请输入内容" @clear="handleClear('status')"><t-option v-for="(item, index) in statusData" :key="index" :value="item.id" :label="item.value"title="" /></t-select></t-form-item></t-col><t-col class="searchBtn"><button type="button" class="bt-grey wt-60" @click="handleReset()">重置</button><button type="button" class="bt wt-60" @click="handleSearch()">搜索</button></t-col></t-row></t-form></div>
</template><script setup lang="ts">import { ref } from 'vue';
import { statusData } from '@/utils/commonData'const form = ref(null);
/* const searchData = ref({name: '',status: 0
}); */
//接收变量
defineProps({searchData: {type: Object,default: () => ({})}
})//声明方法
const emits = defineEmits(['handleReset','handleSearch','handleClear'])//重置搜索框
const handleReset = () =>{emits('handleReset')
}//搜索
const handleSearch = () =>{emits('handleSearch')
}//清空
const handleClear = (val) =>{emits('handleClear',val)
}</script>

TDesign组件参考链接

Grid栅格:TDesign Web Vue Next

Form 表单:TDesign Web Vue Next

Input输入框:TDesign Web Vue Next

Button 按钮:TDesign Web Vue Next

新增护理项目

TDesign弹窗组件:TDesign Web Vue Next

新增一个DialogFrom.vue

<!-- 护理项目新增编辑弹窗 -->
<template><div class="dialog-form"><t-dialogv-model:visible="formVisible":header="title + '护理项目'":footer="false":on-close="onClickCloseBtn"><template #body><!-- 表单内容 --><div class="dialogCenter"><div class="dialogOverflow"><t-formref="form":data="formData":rules="rules":label-width="110":reset-type="resetType"@reset="onClickCloseBtn"@submit="onSubmit"><t-form-item label="护理项目名称:" name="name"><t-inputv-model="formData.name"class="wt-400"placeholder="请输入"clearableshow-limit-number:maxlength="10"></t-input></t-form-item><t-form-item label="价格:" name="price"><t-input-numberv-model="formData.price":min="0":step="10"placeholder="0.00":decimal-places="2"@blur="textBlurPrice"@change="textBlurPrice"></t-input-number></t-form-item><t-form-item label="单位:" name="unit"><t-inputv-model="formData.unit"class="wt-400"placeholder="请输入"clearableshow-limit-number:maxlength="5"></t-input></t-form-item><t-form-item label="排序:" name="orderNo"><t-input-numberv-model="formData.orderNo":min="minNumber"@blur="textBlurNo"@change="textBlurNo"></t-input-number></t-form-item><t-form-item label="状态:" name="status"><t-radio-group v-model="formData.status"><t-radiov-for="(item, index) in statusData":key="index":value="item.id">{{ item.value }}</t-radio></t-radio-group></t-form-item><t-form-item label="护理图片:" name="image"><t-uploadref="uploadRef"v-model="photoFile"action="api/common/upload":autoUpload="autoUpload"theme="image":size-limit="sizeLimit"tips="图片大小不超过2M,仅支持上传PNG JPG JPEG类型图片"accept="image/*":before-upload="beforeUpload"@remove="remove"@fail="handleFail"@success="handleSuccess"></t-upload></t-form-item><t-form-item label="护理项目描述:" name="nursingRequirement"><t-textareav-model="formData.nursingRequirement"class="wt-400"placeholder="请输入"show-limit-number:maxlength="50"></t-textarea></t-form-item><t-form-item class="dialog-footer"><div><button class="bt bt-grey wt-60" type="reset">取消</button><button theme="primary" type="submit" class="bt wt-60"><span>确定</span></button></div></t-form-item></t-form></div></div></template></t-dialog></div></template><script setup lang="ts">import { ref, watch } from 'vue'import { MessagePlugin, ValidateResultContext } from 'tdesign-vue-next'// 基础数据import { statusData } from '@/utils/commonData'// 获取父组件值、方法const props = defineProps({// 弹层隐藏显示visible: {type: Boolean,default: false},//   详情数据data: {type: Object,default: () => {return {}}},// 最小值minNumber: {type: Number,default: 1},// 标题title: {type: String,default: '新增'},})// ------定义变量------// 触发父级事件const emit: Function = defineEmits(['handleClose','fetchData','handleAdd','handleEdit'])const resetType = ref('empty') // 重置表单const form = ref() // 表单const formVisible = ref(false) // 弹窗// 表单数据const formData = ref<Object | any>({status: 1,orderNo: 1})const autoUpload = ref(true) // 是否在选择文件后自动发起请求上传文件const photoFile = ref([]) // 绑定上传的文件const sizeLimit = ref({size: 2,unit: 'MB',message: '图片大小超过2m,请重新上传'}) // 图片的大小限制// 表单校验const rules = {name: [// 名称校验{required: true,message: '护理项目名称为空,请输入护理项目名称',type: 'error',trigger: 'blur'}],// 费用校验price: [{required: true,message: '价格为空,请输入价格',type: 'error',trigger: 'blur'},{validator: (val) => val >= 0.01,message: '价格为空,请输入价格',type: 'error',trigger: 'change'}],// 单位unit: [{required: true,message: '单位为空,请输入单位',type: 'error',trigger: 'blur'}],// 排序orderNo: [{required: true,message: '排序为空,请输入排序',type: 'error',trigger: 'blur'},{validator: (val) => val >= 1,message: '排序为空,请输入排序',type: 'error',trigger: 'change'}],//   状态status: [{required: true,message: '状态为空,请选择状态',type: 'error',trigger: 'change'}],// 护理图片image: [{required: true,message: '护理图片为空,请上传护理图片',type: 'error',trigger: 'change'}],//   项目描述nursingRequirement: [{required: true,message: '护理项目描述为空,请输入护理项目描述',type: 'error',trigger: 'blur'}]}// 弹窗标题const title = ref()// 监听器,监听父级传递的visible值,控制弹窗显示隐藏watch(() => props.visible,() => {formVisible.value = props.visibletitle.value = props.title})// 监听器,监听父级传递的data值,控制表单数据watch(() => props.data,(val) => {formData.value = valconst obj = {url: val.image}photoFile.value.push(obj)})// -----定义方法------// 提交表单const onSubmit = (result: ValidateResultContext<FormData>) => {if (result.validateResult === true) {if (props.title === '新增') {// 调用新增接口emit('handleAdd', formData.value)} else {// 调用编辑接口emit('handleEdit', formData.value)}}}// 清除表单数据const handleClear = () => {// 重置表单form.value.reset()formData.value.orderNo = 1formData.value.status = 1photoFile.value = []}// 点击取消关闭const onClickCloseBtn = () => {handleClear()emit('handleClose')}// // 监听价格const textBlurPrice = () => {const data = Number(formData.value.price)minPrice(data)}// 监听排序const textBlurNo = () => {const data = Number(formData.value.orderNo)minNum(data)}// 当前输入的金额小于0的时候显示0.00const minPrice = (val) => {if (val < 0) {formData.value.fee = '0.00'}}// 当前输入的排序小于等于1的时候显示1const minNum = (val) => {if (val <= 1) {formData.value.orderNo = 1}}// 移除图片时将图片设置为默认图片const remove = () => {photoFile.value = []formData.value.image = ''}// 上传图片失败const handleFail = ({ file }) => {MessagePlugin.error(`图片 ${file.name} 上传失败`)}// 上传成功后触发。const handleSuccess = (params) => {const photo = params.response.dataformData.value.image = photophotoFile.value[0].response.url = photophotoFile.value[0].url = photo}// 限制图片的大小const beforeUpload = (file) => {if (file.size > 2 * 1024 * 1024) {MessagePlugin.error('图片大小超过2M,请重新上传')return false}return true}// 向父组件暴露数据与方法defineExpose({handleClear})</script>

调出弹窗的按钮在列表的左上角:新增护理项目按钮

在TableList组件中新增按钮代码,代码如下:

<div class="newBox"><button class="bt wt-120" @click="handleBulid()">新增护理项目</button>
</div>在js代码中增加方法handleBulid,来打开弹窗
<script setup lang="ts">//声明方法
const emit = defineEmits(['onPageChange','handleBulid'
])//新增按钮
const handleBulid = () =>{emit('handleBulid')
}</script>

在index.vue中去引用,我们在父组件中中去控制visible属性,需要给刚才定义的按钮绑定(新增护理项目)

<template><TableList:data="data":total="total":pagination="pagination"@getCurrent="getCurrent"@isDecimals="isDecimals"@handleBulid="handleBulid"  //注意这里需要在TableList组件中调用父组件的方法></TableList><DialogFrom:visible="visible"@handleClose="handleClose"></DialogFrom>
</template>
<script setup lang="ts">
import DialogFrom  from './components/DialogFrom.vue'
//是否显示弹窗
var visible = ref(false)
//点击新增护理项目 按钮 把visible设置为true,弹出
const handleBulid = () =>{visible.value = true;
}
//点击弹窗中的关闭或取消,关闭弹窗
const handleClose = () =>{visible.value = false;
}
</script>

在src/api/serve.ts文件,定义新增接口,代码如下:

// 护理项目添加
export function projectAdd(params) {return request.post<ProjecListModel>({url: '/nursing_project',data: params})
}

index组件与Dialog组件整合

index.vue中继续完善DialogFrom组件的内容,代码如下:

<template><DialogFromref="formRef":visible="visible":title="title"@handleClose="handleClose"@handle-add="handleAdd"></DialogFrom></template><script setup lang="ts">
import {  onMounted, ref } from 'vue'
import TableList  from './components/TableList.vue'
import SearchFrom  from './components/SearchFrom.vue'
import DialogFrom  from './components/DialogFrom.vue'
import { getProjectList,projectAdd } from '@/api/serve'
import { MessagePlugin } from 'tdesign-vue-next'var visible = ref(false)
const formRef = ref(null)
const title = ref('') // 弹窗标题
const formBaseData = ref({}) // 弹窗表单内容const handleBulid = () =>{title.value = '新增'visible.value = true;
}const handleClose = () =>{visible.value = false;
}// 添加
const handleAdd = async (val) => {const res = await projectAdd(val)if (res.code === 200) {MessagePlugin.success('添加成功')getList()handleClose()formRef.value.handleClear()} else {MessagePlugin.error(res.msg)}
}
</script>
  • formRef就是指子组件,定义为了一个对象
  • 子组件向父组件暴露数据和方法,可以让父组件去执行
// 向父组件暴露数据与方法defineExpose({handleClear})
  • formRef.value.handleClear() 就是父组件中直接执行子组件暴露的方法

编辑护理项目

找到TableList组件中的操作栏,修改编辑a标签

<!-- 操作栏 -->
<template #op="{ row }"><div class="operateCon"><a class="btn-dl">删除</a><a class="font-bt" @click="handleEdit(row)">编辑</a><a class="delete">禁用</a></div>
</template>

在当前组件中,需要调用父组件的方法

//引入该组件,需要传递方法
const emit = defineEmits(['getCurrent','handleBulid','handleEdit'])
//编辑   参数为一行数据
const handleEdit = (row) =>{emit('handleEdit', row)
}

在index.vue组件中添加对应的方法,需要调用接口查询护理项目的详情

<template><TableList :data="data" :total="total" :pagination="pagination" :dataLoading="dataLoading"@onPageChange="onPageChange" @handleBulid="handleBulid" @handleEdit="handleEdit"></TableList><!-- 新增或编辑弹窗 --><DialogFrom ref="formRef":title="title":visible="visible" :data="formBaseData"@handleClose = "handleClose"@handleAdd = "handleAdd" ></DialogFrom></template><script setup lang="ts">
//添加接口
import { getProjectList,projectAdd,getProjectDetails } from '@/api/serve'const title = ref('') // 弹窗标题
const formBaseData = ref({}) // 弹窗表单内容//编辑
const handleEdit = (val) =>{// 将弹窗的标题title.value = '编辑'// 获取详情getDetails(val.id)// 显示弹窗visible.value = true
}// 获取详情数据
const getProjectDetails = async (id) => {const res = await getProjectDetails(id) // 获取列表数据if (res.code === 200) {formBaseData.value = res.data}
}
</script>

在src/api/serve.ts文件,定义查询详情接口,参考Knife4j在线接口文档

// 获取护理项目详情
export function getProjectDetails(id) {return request.get<ProjecListModel>({url: `/nursing_project/${id}`})
}

修改DialogFrom组件,回显数据,这个能够回显的原因有两个

第一:在DialogFrom组件中定义了接收了父组件的data数据

第二:在DialogFrom组件中定义了侦听,一旦数据发生变化就会重新给表单数据赋值

<script setup lang="ts">
import { ref, watch } from 'vue'
// 基础数据
import { statusData } from '@/utils/commonData'
import { MessagePlugin, ValidateResultContext } from 'tdesign-vue-next'
import { rules } from './rules'
const props = defineProps({
// 监听器,监听父级传递的data值,控制表单数据
watch(() => props.data,(val) => {formData.value = valconst obj = {url: val.image}photoFile.value.push(obj)}
)
</script>

我们现在打开编辑弹窗,数据就可以回显了,效果如下:

  • 修改数据

我们刚才是回显了护理项目的详细数据,现在当我们修改了数据之后,点击确定就需要调用后端的修改接口了,由于我们之前写过新增,它们的思路基本是一致的,并且新增和编辑复用了弹窗,我们现在只需要编写修改的接口即可。

在src/api/serve.ts文件,定义修改护理项目的接口,参考Knife4j在线接口文档

// 护理项目编辑
export function projectUpdate(params: ProjecListModel) {return request.put<ProjecListModel>({url: `/nursing_project`,data: params})
}

由于我们之前在DialogFrom表单中已经定义了修改的方法,并且让它去调用了父组件的方法,我们现在只需要在父组件中去定义修改方法即可

<template><SearchFrom:searchData="pagination"@handleReset="handleReset"@handleSearch="handleSearch"></SearchFrom><TableList:data="data":total="total":pagination="pagination"@getCurrent="getCurrent"@isDecimals="isDecimals"@handleBulid="handleBulid"@handleEdit="handleEdit"></TableList><DialogFromref="formRef":visible="visible":data="formBaseData":title="title"@handleClose="handleClose"@handle-add="handleAdd"@handleEdit="handleEditForm"></DialogFrom></template><script setup lang="ts">
import { getProjectList,projectAdd,getProjectDetails,projectUpdate } from '@/api/serve'// 修改数据
const handleEditForm = async (val) => {const res = await projectUpdate(val)if (res.code === 200) {MessagePlugin.success('编辑成功')getList()handleClose()formRef.value.handleClear()} else {MessagePlugin.error(res.msg)}
}//编辑
const handleEdit = (val) =>{// 将弹窗的标题title.value = '编辑'// 获取详情getDetails(val.id)// 显示弹窗visible.value = true
}
</script>

大家注意:

在index.vue中有两个Edit方法,它们的作用是不同的

  • handleEdit 被列表中的编辑按钮触发,作用是打开弹窗,获取详情,在TableList被引用
  • handleEditForm 弹窗中的确定按钮触发,作用是修改数据 ,在DialogFrom中被引用

删除护理项目

当我们点击了删除按钮,就会先弹出一个确认框,再来决定是否需要删除

由于像这样的删除弹窗,在当前项目中的很普遍,应用的地方很多,所以,像这样的弹窗都会封装为一个公共的组件来让项目使用

我们项目中公共组件位置在src/components目录中

其中的删除组件在src/components/OperateDialog/index.vue

我们先来分析一下这个代码:

<!--操作弹层-->
<template><div class="deleteDialog baseDialog"><t-dialogv-model:visible="dialogVisible":header="title ? title : '确认删除'":footer="false":on-close="handleClose":on-confirm="handleSubmit"><div v-if="title === '确认驳回'">驳回申请后,该流程将自动驳回至发起人,是否继续?</div><div v-else-if="title === '确认提交'">账单审批通过后,应退金额不可再次修改。完成退款操作后,退款金额将退到预缴款余额中,最终随退住办理完结时一起退还给老人,是否确定提交账单?</div><div v-else><div v-if="text">此操作将{{ text }},是否继续?</div><div v-else>此操作将删除该{{ deleteText }},是否继续?</div></div><!-- 此操作将永久删除这条信息,是否继续? --><div class="dialog-footer"><buttontheme="primary"type="submit"class="bt-grey wt-60"@click="handleClose"><span>取消</span></button><buttontheme="primary"type="submit"class="bt wt-60"@click="handleSubmit"><span>确定</span></button></div></t-dialog></div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
// 获取父组件值、方法
const props = defineProps({// 弹层隐藏显示visible: {type: Boolean,default: false},title: {type: String,default: ''},text: {type: String,default: ''},deleteText: {type: String,default: ''}
})
// ------定义变量------
const emit = defineEmits(['handleClose', 'handleDelete']) // 子组件获取父组件事件传值
const dialogVisible = ref(false)
watch(() => props.visible,(newVal) => {dialogVisible.value = newVal}
)
// ------定义方法------
// 关闭弹层
const handleClose = () => {emit('handleClose')
}
// 提交确定删除
const handleSubmit = () => {emit('handleDelete')
}
</script>

我们想要使用这个组件,想要传入几个值

  • :visible 控制弹窗的调出
  • :deleteText 删除的具体项目业务提示 ,比如:此操作将删除该护理项目,是否继续?
  • handleClose 关闭弹窗的方法
  • handleDelete 调用后端删除接口,执行数据删除

下面我们就进入代码开发,首先我们要做的是调出该弹窗,然后才会执行删除

调出弹窗是在列表页中的”删除”按钮

我们需要修改TableList组件,来调出弹窗,代码如下:

<template><div class="newBox"><button class="bt wt-120" @click="handleBulid()">新增护理项目</button></div><!-- 当数据为空需要占位时,会显示 cellEmptyContent --><t-tablerowKey="index":data="data":columns="COLUMNS":stripe="stripe":bordered="bordered":hover="hover":table-layout="tableLayout ? 'auto' : 'fixed'":size="size":pagination="pagination.total > 10 ? pagination : null":show-header="showHeader"cell-empty-content="-"resizable@page-change="getCurrent"><!-- 操作栏 --><template #op="{ row }"><div class="operateCon"><a class="btn-dl" @click="handleClickDelete(row)">删除</a><a class="font-bt" @click="handleEdit(row)">编辑</a><a class="delete">禁用</a></div></template></t-table>
</template><script setup lang="ts">//引入该组件,需要传递方法
const emit = defineEmits(['getCurrent','isDecimals','handleBulid','handleEdit','handleClickDelete'
])// 点击删除
const handleClickDelete = (row) => {emit('handleClickDelete', row)
}</script>

准备删除接口

在src/api/serve.ts文件,定义删除护理项目的接口,参考Knife4j在线接口文档

// 护理项目删除
export function projectDelete(id) {return request.delete({url: `/nursing_project/${id}`})
}

然后在index.vue中添加handleClickDelete方法来控制弹窗的出现,不过,我们也需要在index.vue中引入delete弹窗,代码如下:

<template><TableList:data="data":total="total":pagination="pagination"@getCurrent="getCurrent"@isDecimals="isDecimals"@handleBulid="handleBulid"@handleEdit="handleEdit"@handleClickDelete="handleClickDelete"></TableList><!-- 删除弹层 --><Delete:visible="dialogDeleteVisible":delete-text="operateText"@handle-delete="handleDelete"@handle-close="handleDeleteClose"></Delete></template><script setup lang="ts">
// 删除弹层
import Delete from '@/components/OperateDialog/index.vue'
import { getProjectList,projectAdd,getProjectDetails,projectUpdate,projectDelete } from '@/api/serve'const dialogDeleteVisible = ref(false) // 控制删除弹层显示隐藏
const operateText = ref('护理项目') // 要操作的内容提示
const typeId = ref('') // 设置删除id// 确认删除
const handleDelete = async () => {const res= await projectDelete(typeId.value)if (res.code === 200) {dialogDeleteVisible.value = falseMessagePlugin.success('删除成功')getList()}
}
// 点击删除
const handleClickDelete = (val) => {typeId.value = val.iddialogDeleteVisible.value = true
}// 关闭删除弹层
const handleDeleteClose = () => {dialogDeleteVisible.value = false
}</script>
  • 在TableList组件传递handleClickDelete,并编写handleClickDelete方法逻辑
  • 引入Delete组件
    • 方法:handle-close 关闭删除弹窗
    • 方法:handle-delete 调用接口删除
    • 属性:visible 控制删除弹层显示隐藏
    • 属性:delete-text 要操作的内容提示

启用禁用护理项目

跟删除弹窗类似,在点击禁用的时候,也会出现弹窗,效果如下

需求回顾:只有禁用才会有弹窗确认提示,如果是启用,则不会弹窗,直接启用

因为这个功能也是通用的,在项目也已经提供了公共的组件

禁用组件路径:src/components/Forbidden/index.vue

<!--删除弹层-->
<template><div class="deleteDialog baseDialog"><t-dialogv-model:visible="dialogVisible"header="确认禁用":footer="false":on-close="handleClose":on-confirm="handleSubmit">此操作将禁用该{{ text }},是否继续?<div class="dialog-footer"><buttontheme="primary"type="submit"class="bt-grey wt-60"@click="handleClose"><span>取消</span></button><buttontheme="primary"type="submit"class="bt wt-60"@click="handleSubmit"><span>确定</span></button></div></t-dialog></div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
// 获取父组件值、方法
const props = defineProps({// 弹层隐藏显示visible: {type: Boolean,default: false},text: {type: String,default: ''}
})
// ------定义变量------
const emit = defineEmits(['handleClose', 'handleSubmit']) // 子组件获取父组件事件传值
const dialogVisible = ref(false)
watch(() => props.visible,(newVal, oldVal) => {dialogVisible.value = newVal}
)
// ------定义方法------
// 关闭弹层
const handleClose = () => {emit('handleClose')
}
// 提交确定删除
const handleSubmit = () => {emit('handleSubmit')
}
</script>

我们想要使用这个组件,想要传入几个值

  • :visible 控制弹窗的调出
  • :text 禁用的具体项目业务提示 ,比如:此操作将禁用该护理项目,是否继续?
  • handleClose 关闭弹窗的方法
  • handleSubmit 调用后端禁用接口,执行禁用

下面我们就进入代码开发,首先我们要做的是调出该弹窗,然后才会执行禁用

调出弹窗是在列表页中的”禁用”按钮

我们需要修改TableList组件,来调出弹窗,并且我们也发现了,如果是启用是绿色按钮,如果是禁用是红色按钮,这个需要使用状态的不同来控制按钮的颜色,代码如下:

<template><div class="newBox"><button class="bt wt-120" @click="handleBulid()">新增护理项目</button></div><!-- 当数据为空需要占位时,会显示 cellEmptyContent --><t-tablerowKey="index":data="data":columns="COLUMNS":stripe="stripe":bordered="bordered":hover="hover":table-layout="tableLayout ? 'auto' : 'fixed'":size="size":pagination="pagination.total > 10 ? pagination : null":show-header="showHeader"cell-empty-content="-"resizable@page-change="getCurrent"><!-- 操作栏 --><template #op="{ row }"><div class="operateCon"><a class="btn-dl" @click="handleClickDelete(row)">删除</a><a class="font-bt" @click="handleEdit(row)">编辑</a><aclass="delete":class="row.status === 1 ? 'btn-dl' : 'font-bt'"@click="handleForbidden(row)">{{ row.status === 1 ? '禁用' : '启用' }}</a></div></template></t-table>
</template><script setup lang="ts">
import { watch, ref } from 'vue'
import { COLUMNS } from '../constants'//引入该组件,需要传递方法
const emit = defineEmits(['getCurrent','isDecimals','handleBulid','handleEdit','handleClickDelete','handleForbidden'
])// 禁用
const handleForbidden = (row) => {emit('handleForbidden', row)
}
</script>

准备删除接口

在src/api/serve.ts文件,定义禁用护理项目的接口,参考Knife4j在线接口文档

// 护理项目禁用启用
export function projectStatus(params) {return request.put({url: `/nursing_project/${params.id}/status/${params.status}`})
}

然后在index.vue中添加handleForbidden方法来控制弹窗的出现,不过,我们也需要在index.vue中引入禁用弹窗,代码如下:

<template><TableList:data="data":total="total":pagination="pagination"@getCurrent="getCurrent"@isDecimals="isDecimals"@handleBulid="handleBulid"@handleEdit="handleEdit"@handleClickDelete="handleClickDelete"@handleForbidden="handleForbidden"></TableList><!-- 禁用弹层 --><Forbidden:visible="dialogVisible":text="operateText"@handle-submit="handleForbiddenSub"@handle-close="handleForbiddenClose"></Forbidden></template><script setup lang="ts">
import {  onMounted, ref } from 'vue'
import TableList  from './components/TableList.vue'
import SearchFrom  from './components/SearchFrom.vue'
import DialogFrom  from './components/DialogFrom.vue'
// 删除弹层
import Delete from '@/components/OperateDialog/index.vue'
// 禁用弹窗
import Forbidden from '@/components/Forbidden/index.vue'
import { getProjectList,projectAdd,getProjectDetails,projectUpdate,projectDelete,projectStatus } from '@/api/serve'
import { MessagePlugin } from 'tdesign-vue-next'var data = ref([])
var total = ref(0)
var visible = ref(false)
const formRef = ref(null)
const title = ref('') // 弹窗标题
const formBaseData = ref({}) // 弹窗表单内容
const dialogDeleteVisible = ref(false) // 控制删除弹层显示隐藏
const operateText = ref('护理项目') // 要操作的内容提示
const typeId = ref('') // 设置删除id
const dialogVisible = ref(false);
const typeStatus = ref(null) // 禁用启用
const statusText = ref('') // 启用禁用提示//确定禁用
const handleForbiddenSub = async () =>{const params = {id: typeId.value,status: typeStatus.value}const res = await projectStatus(params)if (res.code === 200) {dialogVisible.value = falseMessagePlugin.success(statusText.value)getList()}
}// 禁用弹窗
const handleForbidden = (val) => {typeId.value = val.idif (val.status === 1) {dialogVisible.value = truetypeStatus.value = 0statusText.value = '禁用成功'} else {typeStatus.value = 1handleForbiddenSub()statusText.value = '启用成功'}
}// 关闭禁用弹窗
const handleForbiddenClose = () => {dialogVisible.value = false
}</script>
const handleForbiddenClose = () => {
dialogVisible.value = false
}</script>
  • 在TableList组件传递handleForbidden,并编写handleForbidden方法逻辑
  • 引入Forbidden组件
    • 方法:handleForbiddenClose 关闭禁用弹窗
    • 方法:handleForbiddenSub 调用接口启用或禁用
    • 属性:dialogVisible 控制禁用弹层显示隐藏
    • 属性:operateText 要操作的内容提示
    • 属性:typeId 护理项目id临时存储
    • 属性:typeStatus 护理项目状态临时存储(禁用 | 启用)
http://www.xdnf.cn/news/1087039.html

相关文章:

  • Zookeeper是如何解决脑裂问题的?
  • 深入了解linux系统—— System V之消息队列和信号量
  • 从0到1搭建ELK日志收集平台
  • 扣子Coze纯前端部署多Agents
  • 使用python的 FastApi框架开发图书管理系统-前后端分离项目分享
  • 暑假算法日记第四天
  • Django双下划线查询
  • 汽车功能安全系统阶段开发【技术安全方案TSC以及安全分析】5
  • 基于Vue 3的AI前端框架汇总及工具对比表
  • HTTP/3.x协议详解:基于QUIC的下一代Web传输协议
  • react的条件渲染【简约风5min】
  • 图像梯度处理与边缘检测:OpenCV 实战指南
  • AIGC与影视制作:技术革命、产业重构与未来图景
  • 无缝矩阵的音频合成与音频分离功能详解
  • 静态路由实验以及核心原理
  • 音频主动降噪技术
  • 2025年深圳杉川机器人性格测评和Verify测评SHL题库高分攻略
  • Ubuntu22.04中Google浏览器138版本无法使用中文搜狗输入法
  • AI开源伦理临大考,如何判定抄袭
  • nng库使用
  • 数据结构:位图
  • 无缝矩阵支持音频分离带画面分割功能的全面解析
  • 进阶向:Python音频录制与分析系统详解,从原理到实践
  • 代码详细注释:ARM-Linux字符设备驱动开发案例:LCD汉字输出改进建议开发板断电重启还能显示汉字,显示汉字位置自定义
  • 关于 c、c#、c++ 三者区别
  • linux操作系统---MySQL Galera Cluster部署
  • Spring生态创新应用
  • 软件架构升级中的“隐形地雷”:版本选型与依赖链风险
  • JDBC 注册驱动的常用方法详解
  • 医疗AI底层能力全链条工程方案:从技术突破到临床落地