【JS Utils】创建手动操作的 Promise (可跳过或中断)

创建可手动操作的 JS Promise

createManualPromise(创建手动操作的 Promise)


源码

/**
 *  创建手动操作的 Promise (可跳过或中断)
 *  @param  {Function|Promise}  executor          处理方法 ( Function | Promise: 仅pending状态可手动操作) )
 *  @param  {Object}            [options]         配置项
 *  @param  {Function}          [options.abort]   中断方法
 *  @param  {AbortSignal}       [options.signal]  中断信号
 *  @return {Promise}                             Promise: { then, catch, finally, next: 跳过方法, abort: 中断方法, aborted: 是否已中断, abortedReason: 中断原因, throwIfAborted: 中断时抛出异常方法 }
 */
function createManualPromise(executor, options) {
   
   
  let {
   
    abort, signal = {
   
   } } = options || {
   
   }
  let aborted = signal.aborted || false
  let resolve, reject, properties = {
   
   
    then: {
   
    value() {
   
    return manual(Promise.prototype.then.apply(this, arguments)) } },
    catch: {
   
    value() {
   
    return manual(Promise.prototype.catch.apply(this, arguments)) } },
    finally: {
   
    value() {
   
    return manual(Promise.prototype.finally.apply(this, arguments)) } },
    next: 
// D:\桌面\大学\大四上\vue3_vite_elementplus_admin\src\store\modules\info.js import { defineStore } from "pinia"; import { GetInfoList, GetAllCategory, ChangeInforStatus, UploadFile, CreateInfo, UpdateInfo, GetInfoDetail, DeleteInfo, BatchDeleteInfo, } from "@/api/info"; export const useInfoStore = defineStore("info", () => { function getInfoListAction(requestData) { return new Promise((resolve, reject) => { GetInfoList(requestData).then(resolve).catch(reject); }); } function getAllCategoryAction() { return new Promise((resolve, reject) => { GetAllCategory().then(resolve).catch(reject); }); } function changeInfoStatusAction(requestData) { const { id, data } = requestData; return new Promise((resolve, reject) => { ChangeInforStatus(id, data) .then((response) => { resolve(response); }) .catch((error) => { reject(error); }); }); } function uploadFileAction(requestData = {}) { return new Promise((resolve, reject) => { UploadFile(requestData) .then((response) => resolve(response)) .catch((error) => reject(error)); }); } function createInfoAction(data = {}) { return CreateInfo(data).then((res) => { if (res.code !== 0) return Promise.reject(new Error(res.msg || "创建失败")); return res; }); } function updateInfoAction({ id, data }) { return UpdateInfo(id, data).then((res) => { if (res.code !== 0) return Promise.reject(new Error(res.msg || "更新失败")); return res; }); } function getInfoDetailAction(id) { return new Promise((resolve, reject) => { GetInfoDetail(id) .then((response) => resolve(response)) .catch((error) => reject(error)); }); } function deleteInfoAction(id) { return new Promise((resolve, reject) => { DeleteInfo(id) .then((response) => resolve(response)) .catch((error) => reject(error)); }); } function batchDeleteInfoAction(ids) { return new Promise((resolve, reject) => { BatchDeleteInfo(ids) .then((response) => resolve(response)) .catch((error) => reject(error)); }); } return { getInfoListAction, getAllCategoryAction, changeInfoStatusAction, uploadFileAction, createInfoAction, updateInfoAction, getInfoDetailAction, deleteInfoAction, batchDeleteInfoAction, }; }); // D:\桌面\大学\大四上\vue3_vite_elementplus_admin\src\utils\request.js import axios from "axios"; import { ElMessage } from "element-plus"; import { getToken } from "@/utils/token"; import useStore from "@/store"; import router from "@/router"; // console.log("BASE_URL:", import.meta.env.VITE_APP_BASE_URL); const instance = axios.create({ baseURL: "/devApi", timeout: 20000, }); // 请求拦截器 instance.interceptors.request.use( function (config) { const token = getToken(); if (token) { config.headers["token"] = token; } return config; }, function (error) { return Promise.reject(error); } ); // 响应拦截器 instance.interceptors.response.use( function (response) { const date = response.data; if (date.code == 0) { return Promise.resolve(date); } else { // ElMessage.error(date.msg); return Promise.reject(date); } }, function (error) { console.log(" error ", error); const response = error.request.response; if (response) { const errorData = JSON.parse(response); if (errorData.message || errorData.msg) { ElMessage.error(errorData.message || errorData.msg); } //系统强制退出(token失效,已经登出) if (errorData.message === 999) { const store = useStore(); store.app.updateToken(""); router.replace({ name: "Login" }); } } else { ElMessage.error("网络连接超时"); } return Promise.reject(error); } ); export default instance; <!-- D:\桌面\大学\大四上\vue3_vite_elementplus_admin\src\views\info\Detail.vue --> <template> <el-form label-width="90px" :model="form" :rules="rules" ref="formRef"> <el-form-item label="类别" prop="category_id"> <el-cascader v-model="category" :props="category_props" :options="categoryData.category_options" filterable clearable style="width:100%" @change="handleCategoryChange" /> </el-form-item> <el-form-item label="信息标题" prop="title"> <el-input v-model="form.title" /> </el-form-item> <el-form-item label="缩略图" prop="image_url"> <el-upload ref="uploadRef" class="avatar-uploader" action="#" :limit="1" :multiple="false" :auto-upload="false" :show-file-list="false" :on-change="handleChange"> <img v-if="form.image_url" :src="form.image_url" class="avatar" /> <el-icon v-else class="avatar-uploader-icon"> <Plus /> </el-icon> </el-upload> </el-form-item> <el-form-item label="内容" prop="content"> <div style="border:1px solid #ccc; width: 180%;"> <Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" /> <Editor style="height: 300px; overflow-y: hidden;" v-model="valueHtml" :defaultConfig="editorConfig" :mode="mode" @onCreated="handleEditorCreated" @onChange="handleEditorChange" /> </div> </el-form-item> <el-form-item> <el-button type="danger" :loading="submitLoading" @click="handleSubmit">确定</el-button> <el-button @click="handleCancel">返回</el-button> </el-form-item> </el-form> </template> <script setup> import { reactive, toRefs, ref, shallowRef, onBeforeMount, onBeforeUnmount, watch } from 'vue' import { useRouter, useRoute } from 'vue-router' import '@wangeditor/editor/dist/css/style.css' import { Editor, Toolbar } from '@wangeditor/editor-for-vue' import { useCategoryHook } from '@/hooks/categoryHook' import useStore from '@/store' import { ElMessage, ElMessageBox } from 'element-plus' import { Plus } from '@element-plus/icons-vue' // 新增:导入Plus图标 const router = useRouter() const route = useRoute() const store = useStore() const { categoryData, getAllCategory } = useCategoryHook() const formRef = ref(null) const uploadRef = ref(null) const editorRef = shallowRef() const valueHtml = ref('') const mode = 'default' const toolbarConfig = { excludeKeys: ['group-video', 'insertTable', 'fullScreen'] } const editorConfig = { placeholder: '请输入内容...' } const data = reactive({ category: [], category_props: { label: 'name', value: 'id', checkStrictly: false }, file: null, form: { title: '', image_url: '', content: '', category_id: null }, rules: { category_id: [{ required: true, message: '请选择一个类别', trigger: 'change' }], title: [ { required: true, message: '请输入标题', trigger: 'blur' }, { min: 2, max: 50, message: '标题长度为2-50个字符', trigger: 'blur' } ], image_url: [{ required: true, message: '请上传缩略图', trigger: 'change' }], content: [{ required: true, message: '请输入内容', trigger: 'change' }] }, submitLoading: false, isEdit: false, infoId: null }) const { category, category_props, form, rules, submitLoading } = toRefs(data) const resetForm = () => { data.isEdit = false data.infoId = null data.file = null data.category = [] form.value = { title: '', image_url: '', content: '', category_id: null } valueHtml.value = '' if (formRef.value) { formRef.value.resetFields() } } const getInfoDetail = async (id) => { try { submitLoading.value = true const res = await store.info.getInfoDetailAction(id) const info = res.data form.value.title = info.title || '' form.value.content = info.content || '' form.value.image_url = info.public_image_url || '' if (info.category_id) { const categoryPath = findPathInTree(categoryData.category_options, info.category_id) data.category = categoryPath || [info.category_id] form.value.category_id = info.category_id } valueHtml.value = info.content || '' } catch (error) { console.error('获取详情失败:', error) ElMessage.error(error.msg || '获取信息详情失败') } finally { submitLoading.value = false } } watch(() => route.query.id, async (newId) => { if (newId) { data.isEdit = true data.infoId = newId await getInfoDetail(newId) } else { resetForm() } }, { immediate: true }) function findPathInTree(tree, targetId, path = []) { for (const node of tree) { const currentPath = [...path, node.id] if (node.id === targetId) { return currentPath } if (node.children && node.children.length > 0) { const found = findPathInTree(node.children, targetId, currentPath) if (found) return found } } return null } onBeforeMount(async () => { await getAllCategory() const id = route.query.id if (id) { await getInfoDetail(id) } }) onBeforeUnmount(() => { if (editorRef.value) { editorRef.value.destroy() } }) const handleEditorCreated = (editor) => { editorRef.value = editor } const handleEditorChange = (editor) => { form.value.content = editor.getHtml() formRef.value?.validateField('content') } const handleCategoryChange = () => { form.value.category_id = category.value?.length ? category.value[category.value.length - 1] : null formRef.value?.validateField('category_id') } const checkUploadFile = (rawFile) => { if (rawFile.size / 1024 / 1024 > 2) { ElMessage.error('上传的文件大小不能超过2MB!') return false } const fileType = rawFile.type.startsWith('image/') if (!fileType) { ElMessage.error('请上传图片格式的文件!') return false } return true } const handleChange = (file) => { if (!checkUploadFile(file.raw)) return data.file = file.raw form.value.image_url = URL.createObjectURL(file.raw) uploadRef.value.clearFiles() formRef.value?.validateField('image_url') } const handleSubmit = () => { form.value.content = valueHtml.value formRef.value.validate(async (valid) => { if (!valid) { ElMessage.error('请检查表单填写') return } const fd = new FormData() fd.append('category_id', form.value.category_id) fd.append('title', form.value.title) fd.append('content', form.value.content) if (data.file) { fd.append('file', data.file) } try { submitLoading.value = true if (data.isEdit) { await store.info.updateInfoAction({ id: data.infoId, data: fd }) ElMessage.success('更新成功!') } else { await store.info.createInfoAction(fd) ElMessage.success('创建成功!') } router.push({ name: 'InfoList' }) } catch (error) { ElMessage.error(error.msg || '操作失败,请重试') } finally { submitLoading.value = false } }) } const handleCancel = () => { ElMessageBox.confirm( '确定要离开吗?未保存的内容将会丢失。', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'info', } ).then(() => { router.back() }).catch(() => { }) } </script> <style scoped> .avatar { width: 178px; height: 178px; object-fit: cover; display: block; } </style> <style> .avatar-uploader .el-upload { border: 1px dashed var(--el-border-color); border-radius: 6px; display: inline-block; cursor: pointer; position: relative; overflow: hidden; transition: var(--el-transition-duration-fast); } .avatar-uploader .el-upload:hover { border-color: var(--el-color-primary); } .el-icon.avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 178px; height: 178px; text-align: center; display: flex; align-items: center; justify-content: center; } .w-e-text img { max-width: 100%; height: auto; } </style> <!-- D:\桌面\大学\大四上\vue3_vite_elementplus_admin\src\views\info\index.vue --> <template> <div class="form-button-group margin-bottom-20"> <router-link :to="{ name: 'InfoDetail' }"> <el-button type="danger">新增</el-button> </router-link> </div> <el-form :inline="true" ref="searchFormRef"> <el-form-item label="类别" class="type"> <el-cascader v-model="category" :props="category_props" :options="categoryData.category_options" filterable clearable style="width:100%" /> </el-form-item> <el-form-item label="关键词" prop="key"> <el-select v-model="keywords.key" placeholder="请选择"> <el-option v-for="item in keywords.options" :key="item.value" :value="item.value" :label="item.label"></el-option> </el-select> </el-form-item> <el-form-item prop="value"> <el-input placeholder="请输入关键词" v-model="keywords.value" clearable /> </el-form-item> <el-form-item> <el-button type="danger" @click="handleSearch">搜索</el-button> <el-button type="default" @click="handleReset">重置</el-button> </el-form-item> </el-form> <el-table :data="infoList" border style="width: 100%;" @selection-change="handleSelectionChange" v-loading="loading"> <el-table-column type="selection" width="55" /> <el-table-column label="标题" prop="title" width="200" /> <el-table-column label="类别" prop="category.name" width="100" /> <el-table-column label="创建时间" prop="created_time" width="200" /> <el-table-column label="发布状态" width="120"> <template #default="scope"> <el-switch v-model="scope.row.status" :active-value="1" :inactive-value="0" :loading="scope.row.loading" @change="handleChangeStatus($event, scope.row)" /> </template> </el-table-column> <el-table-column label=" 发布时间" prop="release_time" width="200" /> <el-table-column label="操作"> <template #default="scope"> <el-button size="small" @click="handleEdit(scope.row.id)"> 编辑 </el-button> <el-button size="small" type="danger" @click="handleDelete(scope.row.id)"> 删除 </el-button> </template> </el-table-column> </el-table> <el-row class="margin-top-20"> <el-col :span="6"> <el-button type="danger" :disabled="!row_data_id" @click="handleDelete(row_data_id)">批量删除</el-button> </el-col> <el-col :span="18"> <el-pagination class="pull-right" v-model:current-page="request_data.pageIndex" v-model:page-size="request_data.pageSize" :page-sizes="[10, 20, 30, 50]" background small layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </el-col> </el-row> </template> <script setup> import { ref, reactive, toRefs, onBeforeMount } from 'vue' import { useRouter } from 'vue-router' import useStore from '@/store' import { ElMessage, ElMessageBox } from 'element-plus' import { useCategoryHook } from '@/hooks/categoryHook' const router = useRouter() const store = useStore() const { categoryData, getAllCategory } = useCategoryHook() const data = reactive({ category: 0, category_props: { label: 'name', value: 'id' }, keywords: { key: '', options: [ { label: '标题', value: 'title' }, { label: '内容', value: 'content' } ], value: '' }, infoList: [], total: 0, request_data: { pageIndex: 1, pageSize: 10, }, row_data_id: '', loading: false }) const { keywords, category, category_props, total, request_data, infoList, row_data_id, loading } = toRefs(data) const getInfoList = async (searchParams = {}) => { loading.value = true try { const params = { pageIndex: request_data.value.pageIndex, pageSize: request_data.value.pageSize, ...searchParams } const res = await store.info.getInfoListAction(params) data.infoList = res.data.rows || [] data.total = res.data.count || 0 } catch (error) { console.error('获取列表失败', error) ElMessage.error('获取信息列表失败') } finally { loading.value = false } } onBeforeMount(async () => { await getAllCategory(); getInfoList(); }); const handleSelectionChange = (val) => { console.log('handleSelectionChange val', val); if (val && val.length > 0) { const ids = val.map(item => item.id) row_data_id.value = ids.join() } else { row_data_id.value = "" } } const handleSizeChange = (val) => { console.log(`${val} items per page`) request_data.value.pageSize = val request_data.value.pageIndex = 1 getInfoList() } const handleCurrentChange = (val) => { console.log(`current page: ${val}`) request_data.value.pageIndex = val getInfoList() } const handleSearch = () => { request_data.value.pageIndex = 1; const searchParams = {}; if (category.value?.length) { searchParams.category_id = category.value[category.value.length - 1]; } if (keywords.value.key && keywords.value.value) { searchParams.keywords_key = keywords.value.key; searchParams.keywords_value = keywords.value.value; } getInfoList(searchParams); } const handleReset = () => { category.value = []; keywords.value.key = ''; keywords.value.value = ''; request_data.value.pageIndex = 1; request_data.value.pageSize = 10; getInfoList(); } const handleChangeStatus = async (value, data) => { data.loading = true data.status = !data.status try { const res = await store.info.changeInfoStatusAction({ id: data.id, data: { status: value } }) ElMessage.success(res.msg) await getInfoList() data.status = value } finally { data.loading = false } } const handleDelete = (id) => { ElMessageBox.confirm( '确定要删除当前信息吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', } ) .then(async () => { const res = await store.info.deleteInfoAction(id) ElMessage.success(res.msg) await getInfoList() }) } const handleEdit = (id) => { console.log('handleEdit id', id); router.push({ name: 'InfoDetail', query: { id } }) } </script> <style scoped> .el-input { --el-input-width: 220px; } .el-select { --el-select-width: 220px; } </style> // D:\桌面\大学\大四上\vue3_vite_elementplus_admin\src\api\info.js import installer from "@/utils/request"; export function GetInfoList(params = {}) { return installer.request({ url: "/infos", method: "GET", params, }); } export function ChangeInfoStatus(id, data = {}) { return installer.request({ url: `/infos/${id}/status`, method: "PUT", data, }); } export function GetAllCategory(params) { return installer.request({ url: "/categories", method: "GET", params, }); } export function ChangeInforStatus(id, data = {}) { return installer.request({ url: `/infos/${id}/status`, method: "PUT", data, }); } export function UploadFile(data = {}) { return installer.request({ url: "/upload", method: "POST", data, }); } export function CreateInfo(data = {}) { return installer.request({ url: "/infos", headers: { "Content-Type": "multipart/form-data" }, method: "POST", data, }); } export function UpdateInfo(id, data = {}) { return installer.request({ url: `/infos/${id}`, method: "PUT", data, }); } export function GetInfoDetail(id) { return installer.request({ url: `/infos/${id}`, method: "GET", }); } export function DeleteInfo(id) { return installer.request({ url: `/infos/${id}`, method: "DELETE", }); } export function BatchDeleteInfo(ids) { const idsStr = Array.isArray(ids) ? ids.join(",") : ids; return installer.request({ url: `/infos/${idsStr}`, method: "DELETE", }); } 为什么新增和编辑弹出成功提示后不跳转,并且还弹出错误提示
最新发布
10-17
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值