review:【antd】【mp】图文草稿箱
This commit is contained in:
@@ -149,6 +149,7 @@ const showCreateButton = computed(() => {
|
|||||||
return tableDataLength.value <= 0;
|
return tableDataLength.value <= 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO @hw:看看能不能参考 tag/index.vue 简化下
|
||||||
/** 页面挂载后,等待表单初始化完成再加载数据 */
|
/** 页面挂载后,等待表单初始化完成再加载数据 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 等待 WxAccountSelect 组件加载并设置默认值
|
// 等待 WxAccountSelect 组件加载并设置默认值
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import { Button, Image, message, Modal, Upload } from 'ant-design-vue';
|
|||||||
import { UploadType, useBeforeUpload } from '#/utils/useUpload';
|
import { UploadType, useBeforeUpload } from '#/utils/useUpload';
|
||||||
import { WxMaterialSelect } from '#/views/mp/modules/wx-material-select';
|
import { WxMaterialSelect } from '#/views/mp/modules/wx-material-select';
|
||||||
|
|
||||||
// 设置上传的请求头部
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
isFirst: boolean;
|
isFirst: boolean;
|
||||||
modelValue: NewsItem;
|
modelValue: NewsItem;
|
||||||
@@ -54,10 +52,11 @@ function onMaterialSelected(item: any) {
|
|||||||
newsItem.value.thumbMediaId = item.mediaId;
|
newsItem.value.thumbMediaId = item.mediaId;
|
||||||
newsItem.value.thumbUrl = item.url;
|
newsItem.value.thumbUrl = item.url;
|
||||||
}
|
}
|
||||||
|
// TODO @hw:注释都补充下哈;
|
||||||
const onBeforeUpload = (file: UploadFile) =>
|
const onBeforeUpload = (file: UploadFile) =>
|
||||||
useBeforeUpload(UploadType.Image, 2)(file as any);
|
useBeforeUpload(UploadType.Image, 2)(file as any);
|
||||||
|
|
||||||
|
// TODO @hw:注释都补充下哈;
|
||||||
function onUploadChange(info: any) {
|
function onUploadChange(info: any) {
|
||||||
if (info.file.status === 'done') {
|
if (info.file.status === 'done') {
|
||||||
onUploadSuccess(info.file.response || info.file);
|
onUploadSuccess(info.file.response || info.file);
|
||||||
@@ -66,6 +65,7 @@ function onUploadChange(info: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @hw:注释都补充下哈;
|
||||||
function onUploadSuccess(res: any) {
|
function onUploadSuccess(res: any) {
|
||||||
if (res.code !== 0) {
|
if (res.code !== 0) {
|
||||||
message.error(`上传出错:${res.msg}`);
|
message.error(`上传出错:${res.msg}`);
|
||||||
@@ -74,12 +74,12 @@ function onUploadSuccess(res: any) {
|
|||||||
|
|
||||||
// 重置上传文件的表单
|
// 重置上传文件的表单
|
||||||
fileList.value = [];
|
fileList.value = [];
|
||||||
|
|
||||||
// 设置草稿的封面字段
|
// 设置草稿的封面字段
|
||||||
newsItem.value.thumbMediaId = res.data.mediaId;
|
newsItem.value.thumbMediaId = res.data.mediaId;
|
||||||
newsItem.value.thumbUrl = res.data.url;
|
newsItem.value.thumbUrl = res.data.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @hw:注释都补充下哈;
|
||||||
function onUploadError(err: Error) {
|
function onUploadError(err: Error) {
|
||||||
message.error(`上传失败: ${err.message}`);
|
message.error(`上传失败: ${err.message}`);
|
||||||
}
|
}
|
||||||
@@ -88,6 +88,7 @@ function onUploadError(err: Error) {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<p>封面:</p>
|
<p>封面:</p>
|
||||||
|
<!-- TODO @hw:我貌似上传不成功。不确定是不是我这边的问题;;;可以微信沟通下哈。 -->
|
||||||
<div class="thumb-div">
|
<div class="thumb-div">
|
||||||
<Image
|
<Image
|
||||||
v-if="newsItem.thumbUrl"
|
v-if="newsItem.thumbUrl"
|
||||||
@@ -115,6 +116,7 @@ function onUploadError(err: Error) {
|
|||||||
<Button size="small" type="primary">本地上传</Button>
|
<Button size="small" type="primary">本地上传</Button>
|
||||||
</template>
|
</template>
|
||||||
</Upload>
|
</Upload>
|
||||||
|
<!-- TODO @hw:tindwind -->
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -146,6 +148,7 @@ function onUploadError(err: Error) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
/** TODO @hw:尽量使用 tindwind 替代。ps:如果多个组件复用,那就不用调整 */
|
||||||
.upload-tip {
|
.upload-tip {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ const props = defineProps<{
|
|||||||
modelValue: NewsItem[] | null;
|
modelValue: NewsItem[] | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// v-model=newsList
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:modelValue', v: NewsItem[]): void;
|
(e: 'update:modelValue', v: NewsItem[]): void;
|
||||||
}>();
|
}>();
|
||||||
@@ -45,6 +44,7 @@ const activeNewsItem = computed(() => {
|
|||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO @hw:注释使用 /** */
|
||||||
// 将图文向下移动
|
// 将图文向下移动
|
||||||
function moveDownNews(index: number) {
|
function moveDownNews(index: number) {
|
||||||
const current = newsList.value[index];
|
const current = newsList.value[index];
|
||||||
@@ -69,14 +69,10 @@ function moveUpNews(index: number) {
|
|||||||
|
|
||||||
// 删除指定 index 的图文
|
// 删除指定 index 的图文
|
||||||
async function removeNews(index: number) {
|
async function removeNews(index: number) {
|
||||||
try {
|
await confirm('确定删除该图文吗?');
|
||||||
await confirm('确定删除该图文吗?');
|
newsList.value.splice(index, 1);
|
||||||
newsList.value.splice(index, 1);
|
if (activeNewsIndex.value === index) {
|
||||||
if (activeNewsIndex.value === index) {
|
activeNewsIndex.value = 0;
|
||||||
activeNewsIndex.value = 0;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// empty
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +230,7 @@ function plusNews() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
/** TODO @hw:尽量使用 tindwind 替代。ps:如果多个组件复用,那就不用调整 */
|
||||||
.ope-row {
|
.ope-row {
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO @hw:要不把 components 里的部分,拿到 modules 里。
|
||||||
interface NewsItem {
|
interface NewsItem {
|
||||||
title: string;
|
title: string;
|
||||||
thumbMediaId: string;
|
thumbMediaId: string;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
|
// TODO @hw:这里的公众号选择,要改参考 /Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/views/mp/tag/data.ts;相关联的代码还简单点~
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|||||||
import { nextTick, onMounted, provide, ref, watch } from 'vue';
|
import { nextTick, onMounted, provide, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { confirm, DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
import { confirm, DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ const [FormModal, formModalApi] = useVbenModal({
|
|||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO @hw:下面的方法,放到这个前面,和别的保持一致;
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
@@ -47,6 +49,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
...formValues,
|
...formValues,
|
||||||
});
|
});
|
||||||
// 处理 API 返回的数据,兼容不同的数据结构
|
// 处理 API 返回的数据,兼容不同的数据结构
|
||||||
|
// TODO @wx:看 yudao-ui-admin-vue3/src/views/mp/draft/index.vue 项目里,转换没这么复杂。。。是不是这里有办法简化下?
|
||||||
const formattedList: Article[] = drafts.list.map((draft: any) => {
|
const formattedList: Article[] = drafts.list.map((draft: any) => {
|
||||||
// 如果已经是 content.newsItem 格式,直接使用
|
// 如果已经是 content.newsItem 格式,直接使用
|
||||||
if (draft.content?.newsItem) {
|
if (draft.content?.newsItem) {
|
||||||
@@ -116,9 +119,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 提供 accountId 给子组件
|
// 提供 accountId 给子组件
|
||||||
|
// TODO @hw:参考 tag/index.vue 放到 formValues.accountId;
|
||||||
const accountId = ref<number>(-1);
|
const accountId = ref<number>(-1);
|
||||||
|
|
||||||
// 监听表单提交,更新 accountId
|
// 监听表单提交,更新 accountId
|
||||||
|
// TODO @hw:看看这个 watch、provide 能不能简化掉;
|
||||||
watch(
|
watch(
|
||||||
() => gridApi.formApi?.getLatestSubmissionValues?.()?.accountId,
|
() => gridApi.formApi?.getLatestSubmissionValues?.()?.accountId,
|
||||||
(newAccountId) => {
|
(newAccountId) => {
|
||||||
@@ -138,7 +143,6 @@ async function handleCreate() {
|
|||||||
message.warning('请先选择公众号');
|
message.warning('请先选择公众号');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
formModalApi
|
formModalApi
|
||||||
.setData({
|
.setData({
|
||||||
isCreating: true,
|
isCreating: true,
|
||||||
@@ -156,7 +160,6 @@ async function handleEdit(row: Article) {
|
|||||||
message.warning('请先选择公众号');
|
message.warning('请先选择公众号');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
formModalApi
|
formModalApi
|
||||||
.setData({
|
.setData({
|
||||||
isCreating: false,
|
isCreating: false,
|
||||||
@@ -171,30 +174,27 @@ async function handleEdit(row: Article) {
|
|||||||
async function handlePublish(row: Article) {
|
async function handlePublish(row: Article) {
|
||||||
const formValues = await gridApi.formApi.getValues();
|
const formValues = await gridApi.formApi.getValues();
|
||||||
const accountId = formValues.accountId;
|
const accountId = formValues.accountId;
|
||||||
|
// TODO @hw:看看能不能去掉 -1 的判断哈?
|
||||||
if (!accountId || accountId === -1) {
|
if (!accountId || accountId === -1) {
|
||||||
message.warning('请先选择公众号');
|
message.warning('请先选择公众号');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await confirm(
|
||||||
const content =
|
|
||||||
'你正在通过发布的方式发表内容。 发布不占用群发次数,一天可多次发布。' +
|
'你正在通过发布的方式发表内容。 发布不占用群发次数,一天可多次发布。' +
|
||||||
'已发布内容不会推送给用户,也不会展示在公众号主页中。 ' +
|
'已发布内容不会推送给用户,也不会展示在公众号主页中。 ' +
|
||||||
'发布后,你可以前往发表记录获取链接,也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。';
|
'发布后,你可以前往发表记录获取链接,也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。',
|
||||||
|
);
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: '发布中...',
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
// TODO @hw:MpFreePublishApi 去掉,直接 import;参考别的模块哈;
|
||||||
try {
|
try {
|
||||||
await confirm(content);
|
await MpFreePublishApi.submitFreePublish(accountId, row.mediaId);
|
||||||
const hideLoading = message.loading({
|
message.success('发布成功');
|
||||||
content: '发布中...',
|
await gridApi.query();
|
||||||
duration: 0,
|
} finally {
|
||||||
});
|
hideLoading();
|
||||||
try {
|
|
||||||
await MpFreePublishApi.submitFreePublish(accountId, row.mediaId);
|
|
||||||
message.success('发布成功');
|
|
||||||
await gridApi.query();
|
|
||||||
} finally {
|
|
||||||
hideLoading();
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,25 +206,21 @@ async function handleDelete(row: Article) {
|
|||||||
message.warning('请先选择公众号');
|
message.warning('请先选择公众号');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await confirm('此操作将永久删除该草稿, 是否继续?');
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: '删除中...',
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
await confirm('此操作将永久删除该草稿, 是否继续?');
|
await MpDraftApi.deleteDraft(accountId, row.mediaId);
|
||||||
const hideLoading = message.loading({
|
message.success('删除成功');
|
||||||
content: '删除中...',
|
await gridApi.query();
|
||||||
duration: 0,
|
} finally {
|
||||||
});
|
hideLoading();
|
||||||
try {
|
|
||||||
await MpDraftApi.deleteDraft(accountId, row.mediaId);
|
|
||||||
message.success('删除成功');
|
|
||||||
await gridApi.query();
|
|
||||||
} finally {
|
|
||||||
hideLoading();
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @hw:看看能不能参考 tag/index.vue 简化下
|
||||||
// 页面挂载后,等待表单初始化完成再加载数据
|
// 页面挂载后,等待表单初始化完成再加载数据
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
@@ -243,6 +239,7 @@ onMounted(async () => {
|
|||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" />
|
<DocAlert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" />
|
||||||
|
|
||||||
|
<!-- TODO @hw:参考别的模块 @success 调用 refresh 方法; -->
|
||||||
<FormModal
|
<FormModal
|
||||||
@success="
|
@success="
|
||||||
() => {
|
() => {
|
||||||
@@ -256,7 +253,7 @@ onMounted(async () => {
|
|||||||
<TableAction
|
<TableAction
|
||||||
:actions="[
|
:actions="[
|
||||||
{
|
{
|
||||||
label: '新增',
|
label: $t('ui.actionTitle.create', ['图文草稿']),
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
icon: ACTION_ICON.ADD,
|
icon: ACTION_ICON.ADD,
|
||||||
auth: ['mp:draft:create'],
|
auth: ['mp:draft:create'],
|
||||||
|
|||||||
@@ -15,12 +15,15 @@ const emit = defineEmits(['success']);
|
|||||||
|
|
||||||
const formData = ref<{
|
const formData = ref<{
|
||||||
accountId: number;
|
accountId: number;
|
||||||
|
// TODO @hw:是不是通过 id 字段判断是否为新增?类似 /Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/views/system/user/modules/form.vue
|
||||||
isCreating: boolean;
|
isCreating: boolean;
|
||||||
mediaId?: string;
|
mediaId?: string;
|
||||||
newsList?: NewsItem[];
|
newsList?: NewsItem[];
|
||||||
}>();
|
}>();
|
||||||
const newsList = ref<NewsItem[]>([]);
|
const newsList = ref<NewsItem[]>([]);
|
||||||
|
// TODO @hw:不需要 isSave,通过 modal 去 lock 就好啦。
|
||||||
const isSubmitting = ref(false);
|
const isSubmitting = ref(false);
|
||||||
|
// TODO @hw:不需要 isSave,通过 modal 去 lock 就好啦。
|
||||||
const isSaved = ref(false);
|
const isSaved = ref(false);
|
||||||
|
|
||||||
const getTitle = computed(() => {
|
const getTitle = computed(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user