feat:【antd】【mp】mp 的代码评审(components)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
export { default as WxAccountSelect } from './wx-account-select/account-select.vue';
|
export { default as WxAccountSelect } from './wx-account-select/wx-account-select.vue';
|
||||||
export { default as WxLocation } from './wx-location/wx-location.vue';
|
export { default as WxLocation } from './wx-location/wx-location.vue';
|
||||||
export { default as WxMaterialSelect } from './wx-material-select/wx-material-select.vue';
|
export { default as WxMaterialSelect } from './wx-material-select/wx-material-select.vue';
|
||||||
export { default as WxMsg } from './wx-msg/msg.vue';
|
export { default as WxMsg } from './wx-msg/msg.vue';
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { default as WxAccountSelect } from './account-select.vue';
|
export { default as WxAccountSelect } from './wx-account-select.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
export * from './types';
|
export * from './types';
|
||||||
export { default as WxLocation } from './wx-location.vue';
|
export { default as WxLocation } from './wx-location.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO @hw:ele 没这个文件,是不是也要搞个?
|
||||||
export interface WxLocationProps {
|
export interface WxLocationProps {
|
||||||
label: string;
|
label: string;
|
||||||
locationX: number;
|
locationX: number;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Col, Row } from 'ant-design-vue';
|
|||||||
|
|
||||||
defineOptions({ name: 'WxLocation' });
|
defineOptions({ name: 'WxLocation' });
|
||||||
|
|
||||||
// TODO @dylan:apps/web-antd/src/views/mall/trade/delivery/pickUpStore/modules/form.vue 参考这个,从后端拿 key 哈
|
// TODO @dylan:@hw:apps/web-antd/src/views/mall/trade/delivery/pickUpStore/modules/form.vue 参考这个,从后端拿 key 哈
|
||||||
const props = withDefaults(defineProps<WxLocationProps>(), {
|
const props = withDefaults(defineProps<WxLocationProps>(), {
|
||||||
qqMapKey: 'TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E', // QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
|
qqMapKey: 'TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E', // QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { default as WxMaterialSelect } from './wx-material-select.vue';
|
export { default as WxMaterialSelect } from './wx-material-select.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { getFreePublishPage } from '#/api/mp/freePublish';
|
|||||||
import { getMaterialPage } from '#/api/mp/material';
|
import { getMaterialPage } from '#/api/mp/material';
|
||||||
import { WxNews, WxVideoPlayer, WxVoicePlayer } from '#/views/mp/components';
|
import { WxNews, WxVideoPlayer, WxVoicePlayer } from '#/views/mp/components';
|
||||||
|
|
||||||
|
/** 微信素材选择 */
|
||||||
defineOptions({ name: 'WxMaterialSelect' });
|
defineOptions({ name: 'WxMaterialSelect' });
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -42,7 +43,7 @@ const queryParams = reactive({
|
|||||||
}); // 查询参数
|
}); // 查询参数
|
||||||
|
|
||||||
const voiceGridColumns: VxeTableGridOptions<any>['columns'] = [
|
const voiceGridColumns: VxeTableGridOptions<any>['columns'] = [
|
||||||
// TODO @dylan:any 有 linter 告警;看看别的模块哈
|
// TODO @hw:@dylan:any 有 linter 告警;看看别的模块哈
|
||||||
{
|
{
|
||||||
field: 'mediaId',
|
field: 'mediaId',
|
||||||
title: '编号',
|
title: '编号',
|
||||||
@@ -77,7 +78,7 @@ const voiceGridColumns: VxeTableGridOptions<any>['columns'] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const videoGridColumns: VxeTableGridOptions<any>['columns'] = [
|
const videoGridColumns: VxeTableGridOptions<any>['columns'] = [
|
||||||
// TODO @dylan:any 有 linter 告警;看看别的模块哈
|
// TODO @hw:@dylan:any 有 linter 告警;看看别的模块哈
|
||||||
{
|
{
|
||||||
field: 'mediaId',
|
field: 'mediaId',
|
||||||
title: '编号',
|
title: '编号',
|
||||||
@@ -381,7 +382,7 @@ watch(
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/** TODO @dylan:看看有没适合 tindwind 的哈。 */
|
/** TODO @dylan:@hw:看看有没适合 tindwind 的哈。 */
|
||||||
@media (width >= 992px) and (width <= 1300px) {
|
@media (width >= 992px) and (width <= 1300px) {
|
||||||
.waterfall {
|
.waterfall {
|
||||||
column-count: 3;
|
column-count: 3;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
||||||
export { default as WxMsg } from './wx-msg.vue';
|
export { default as WxMsg } from './wx-msg.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const props = defineProps<{
|
|||||||
const SendFrom = {
|
const SendFrom = {
|
||||||
MpBot: 2,
|
MpBot: 2,
|
||||||
User: 1,
|
User: 1,
|
||||||
} as const;
|
} as const; // 发送来源
|
||||||
|
|
||||||
function getAvatar(sendFrom: number) {
|
function getAvatar(sendFrom: number) {
|
||||||
return sendFrom === SendFrom.User
|
return sendFrom === SendFrom.User
|
||||||
@@ -63,7 +63,7 @@ function getNickname(sendFrom: number) {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
|
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
|
||||||
|
|
||||||
/** TODO @dylan:看看有没适合 tindwind 的哈。 */
|
/** TODO @dylan:@hw 看看有没适合 tindwind 的哈。 */
|
||||||
|
|
||||||
@import url('./comment.scss');
|
@import url('./comment.scss');
|
||||||
@import url('./card.scss');
|
@import url('./card.scss');
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// TODO @hw:用 MpUserApi 里的 user 可以么?
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
accountId: number;
|
accountId: number;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { default as WxMusic } from './wx-music.vue';
|
export { default as WxMusic } from './wx-music.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { WxMusicProps } from './types';
|
|||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
/** 微信消息 - 音乐 */
|
||||||
defineOptions({ name: 'WxMusic' });
|
defineOptions({ name: 'WxMusic' });
|
||||||
|
|
||||||
const props = withDefaults(defineProps<WxMusicProps>(), {
|
const props = withDefaults(defineProps<WxMusicProps>(), {
|
||||||
@@ -21,8 +22,8 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 微信消息 - 音乐 -->
|
|
||||||
<div>
|
<div>
|
||||||
|
<!-- TODO @hw:是不是用 antd link 更好? -->
|
||||||
<a :href="href" target="_blank" class="text-success no-underline">
|
<a :href="href" target="_blank" class="text-success no-underline">
|
||||||
<div class="music-card">
|
<div class="music-card">
|
||||||
<div class="music-avatar">
|
<div class="music-avatar">
|
||||||
@@ -38,7 +39,7 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/** TODO @dylan:看看有没适合 tindwind 的哈。 */
|
/** TODO @dylan:@hw:看看有没适合 tindwind 的哈。 */
|
||||||
|
|
||||||
.music-card {
|
.music-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { default as WxNews } from './wx-news.vue';
|
export { default as WxNews } from './wx-news.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Image } from 'ant-design-vue';
|
import { Image } from 'ant-design-vue';
|
||||||
|
|
||||||
|
/** 微信消息 - 图文 */
|
||||||
defineOptions({ name: 'WxNews' });
|
defineOptions({ name: 'WxNews' });
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -18,7 +19,6 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 微信消息 - 图文 -->
|
|
||||||
<div class="news-home">
|
<div class="news-home">
|
||||||
<div v-for="(article, index) in articles" :key="index" class="news-div">
|
<div v-for="(article, index) in articles" :key="index" class="news-div">
|
||||||
<!-- 头条 -->
|
<!-- 头条 -->
|
||||||
@@ -52,7 +52,7 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/** TODO @dylan:看看有没适合 tindwind 的哈。 */
|
/** TODO @dylan:@hw:看看有没适合 tindwind 的哈。 */
|
||||||
|
|
||||||
.news-home {
|
.news-home {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
||||||
export { default as WxReply } from './wx-reply.vue';
|
export { default as WxReply } from './wx-reply.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ const emit = defineEmits<{
|
|||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
|
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
|
||||||
const HEADERS = { Authorization: `Bearer ${accessStore.accessToken}` };
|
const HEADERS = { Authorization: `Bearer ${accessStore.accessToken}` };
|
||||||
|
|
||||||
const reply = computed<Reply>({
|
const reply = computed<Reply>({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
set: (val) => emit('update:modelValue', val),
|
set: (val) => emit('update:modelValue', val),
|
||||||
@@ -41,6 +40,7 @@ const uploadData = reactive({
|
|||||||
type: 'image',
|
type: 'image',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 图片上传前校验 */
|
||||||
function beforeImageUpload(rawFile: UploadRawFile) {
|
function beforeImageUpload(rawFile: UploadRawFile) {
|
||||||
return useBeforeUpload(UploadType.Image, 2)(rawFile);
|
return useBeforeUpload(UploadType.Image, 2)(rawFile);
|
||||||
}
|
}
|
||||||
@@ -65,6 +65,7 @@ async function customRequest(options: any) {
|
|||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
|
// TODO @hw:if return 风格,简化掉。if (result.code !=== 0) { ... }
|
||||||
if (result.code === 0) {
|
if (result.code === 0) {
|
||||||
// 清空上传时的各种数据
|
// 清空上传时的各种数据
|
||||||
fileList.value = [];
|
fileList.value = [];
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import {
|
|||||||
import { WxMaterialSelect } from '#/views/mp/components';
|
import { WxMaterialSelect } from '#/views/mp/components';
|
||||||
import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
|
import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
|
||||||
|
|
||||||
|
// TODO @hw:类似 tab-image.vue 的建议
|
||||||
|
|
||||||
defineOptions({ name: 'TabMusic' });
|
defineOptions({ name: 'TabMusic' });
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -46,9 +48,10 @@ const uploadData = reactive({
|
|||||||
accountId: reply.value.accountId,
|
accountId: reply.value.accountId,
|
||||||
introduction: '',
|
introduction: '',
|
||||||
title: '',
|
title: '',
|
||||||
type: 'thumb', // 音乐类型为thumb
|
type: 'thumb', // 音乐类型为 thumb
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 图片上传前校验 */
|
||||||
function beforeImageUpload(rawFile: UploadRawFile) {
|
function beforeImageUpload(rawFile: UploadRawFile) {
|
||||||
return useBeforeUpload(UploadType.Image, 2)(rawFile);
|
return useBeforeUpload(UploadType.Image, 2)(rawFile);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ const reply = computed<Reply>({
|
|||||||
|
|
||||||
const showDialog = ref(false);
|
const showDialog = ref(false);
|
||||||
|
|
||||||
|
/** 选择素材 */
|
||||||
function selectMaterial(item: any) {
|
function selectMaterial(item: any) {
|
||||||
showDialog.value = false;
|
showDialog.value = false;
|
||||||
reply.value.articles = item.content.newsItem;
|
reply.value.articles = item.content.newsItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 删除图文 */
|
||||||
function onDelete() {
|
function onDelete() {
|
||||||
reply.value.articles = [];
|
reply.value.articles = [];
|
||||||
}
|
}
|
||||||
@@ -72,7 +74,6 @@ function onDelete() {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
v-model:open="showDialog"
|
v-model:open="showDialog"
|
||||||
title="选择图文"
|
title="选择图文"
|
||||||
@@ -92,7 +93,7 @@ function onDelete() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/** TODO @dylan:看看有没适合 tindwind 的哈。 */
|
/** TODO @dylan:@hw:看看有没适合 tindwind 的哈。 */
|
||||||
.select-item {
|
.select-item {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const uploadData = reactive({
|
|||||||
type: 'video',
|
type: 'video',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 视频上传前校验 */
|
||||||
function beforeVideoUpload(rawFile: UploadRawFile) {
|
function beforeVideoUpload(rawFile: UploadRawFile) {
|
||||||
return useBeforeUpload(UploadType.Video, 10)(rawFile);
|
return useBeforeUpload(UploadType.Video, 10)(rawFile);
|
||||||
}
|
}
|
||||||
@@ -73,6 +74,7 @@ async function customRequest(options: any) {
|
|||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
|
// TODO @hw:也采用类似 ele 的 if return(res.code !== 0) return 写法;
|
||||||
if (result.code === 0) {
|
if (result.code === 0) {
|
||||||
// 清空上传时的各种数据
|
// 清空上传时的各种数据
|
||||||
fileList.value = [];
|
fileList.value = [];
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const uploadData = reactive({
|
|||||||
type: 'voice',
|
type: 'voice',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 语音上传前校验 */
|
||||||
function beforeVoiceUpload(rawFile: UploadRawFile) {
|
function beforeVoiceUpload(rawFile: UploadRawFile) {
|
||||||
return useBeforeUpload(UploadType.Voice, 10)(rawFile);
|
return useBeforeUpload(UploadType.Voice, 10)(rawFile);
|
||||||
}
|
}
|
||||||
@@ -65,6 +66,7 @@ async function customRequest(options: any) {
|
|||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
|
// TODO @hw:if result.code !== 0 return,代码简洁一点。
|
||||||
if (result.code === 0) {
|
if (result.code === 0) {
|
||||||
// 清空上传时的各种数据
|
// 清空上传时的各种数据
|
||||||
fileList.value = [];
|
fileList.value = [];
|
||||||
@@ -85,12 +87,14 @@ async function customRequest(options: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 删除语音 */
|
||||||
function onDelete() {
|
function onDelete() {
|
||||||
reply.value.mediaId = null;
|
reply.value.mediaId = null;
|
||||||
reply.value.url = null;
|
reply.value.url = null;
|
||||||
reply.value.name = null;
|
reply.value.name = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 选择素材 */
|
||||||
function selectMaterial(item: Reply) {
|
function selectMaterial(item: Reply) {
|
||||||
showDialog.value = false;
|
showDialog.value = false;
|
||||||
reply.value.mediaId = item.mediaId;
|
reply.value.mediaId = item.mediaId;
|
||||||
@@ -165,7 +169,7 @@ function selectMaterial(item: Reply) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/** TODO @dylan:看看有没适合 tindwind 的哈。 */
|
/** TODO @dylan:@hw:看看有没适合 tindwind 的哈。 */
|
||||||
.select-item {
|
.select-item {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 0 auto 10px;
|
margin: 0 auto 10px;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export interface Reply {
|
|||||||
url?: null | string;
|
url?: null | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 利用旧的reply[accountId, type]初始化新的Reply */
|
/** 利用旧的 reply[accountId, type] 初始化新的 Reply */
|
||||||
export function createEmptyReply(old: Ref<Reply> | Reply): Reply {
|
export function createEmptyReply(old: Ref<Reply> | Reply): Reply {
|
||||||
return {
|
return {
|
||||||
accountId: unref(old).accountId,
|
accountId: unref(old).accountId,
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
<!--
|
|
||||||
- Copyright (C) 2018-2019
|
|
||||||
- All rights reserved, Designed By www.joolun.com
|
|
||||||
芋道源码:
|
|
||||||
① 移除多余的 rep 为前缀的变量,让 message 消息更简单
|
|
||||||
② 代码优化,补充注释,提升阅读性
|
|
||||||
③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入
|
|
||||||
④ 支持发送【视频】消息时,支持新建视频
|
|
||||||
-->
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Reply } from './types';
|
import type { Reply } from './types';
|
||||||
|
|
||||||
@@ -25,6 +16,7 @@ import TabVideo from './tab-video.vue';
|
|||||||
import TabVoice from './tab-voice.vue';
|
import TabVoice from './tab-voice.vue';
|
||||||
import { createEmptyReply } from './types';
|
import { createEmptyReply } from './types';
|
||||||
|
|
||||||
|
/** 消息回复选择 */
|
||||||
defineOptions({ name: 'WxReplySelect' });
|
defineOptions({ name: 'WxReplySelect' });
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@@ -34,16 +26,16 @@ const emit = defineEmits<{
|
|||||||
(e: 'update:modelValue', v: Reply): void;
|
(e: 'update:modelValue', v: Reply): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
// TODO @hw:antd 和 ele 风格不同,需要统一;
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: Reply | undefined;
|
modelValue: Reply | undefined;
|
||||||
newsType?: NewsType;
|
newsType?: NewsType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提供一个默认的 Reply 对象,避免 undefined 导致的错误
|
|
||||||
const defaultReply: Reply = {
|
const defaultReply: Reply = {
|
||||||
accountId: -1,
|
accountId: -1,
|
||||||
type: ReplyType.Text,
|
type: ReplyType.Text,
|
||||||
};
|
}; // 提供一个默认的 Reply 对象,避免 undefined 导致的错误
|
||||||
|
|
||||||
const reply = computed<Reply>({
|
const reply = computed<Reply>({
|
||||||
get: () => props.modelValue || defaultReply,
|
get: () => props.modelValue || defaultReply,
|
||||||
@@ -53,6 +45,7 @@ const reply = computed<Reply>({
|
|||||||
const tabCache = new Map<ReplyType, Reply>(); // 作为多个标签保存各自 Reply 的缓存
|
const tabCache = new Map<ReplyType, Reply>(); // 作为多个标签保存各自 Reply 的缓存
|
||||||
const currentTab = ref<ReplyType>(props.modelValue?.type || ReplyType.Text); // 采用独立的 ref 来保存当前 tab,避免在 watch 标签变化,对 reply 进行赋值会产生了循环调用
|
const currentTab = ref<ReplyType>(props.modelValue?.type || ReplyType.Text); // 采用独立的 ref 来保存当前 tab,避免在 watch 标签变化,对 reply 进行赋值会产生了循环调用
|
||||||
|
|
||||||
|
// TODO @hw:antd 和 ele 风格不同,需要统一;
|
||||||
// 监听 modelValue 变化,同步更新 currentTab 和缓存
|
// 监听 modelValue 变化,同步更新 currentTab 和缓存
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
@@ -71,6 +64,7 @@ watch(
|
|||||||
{ immediate: true, deep: true },
|
{ immediate: true, deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO @hw:antd 和 ele 风格不同,需要统一;
|
||||||
watch(
|
watch(
|
||||||
currentTab,
|
currentTab,
|
||||||
(newTab, oldTab) => {
|
(newTab, oldTab) => {
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { default as WxVideoPlayer } from './wx-video-play.vue';
|
export { default as WxVideoPlayer } from './wx-video-play.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Modal } from 'ant-design-vue';
|
|||||||
|
|
||||||
import 'video.js/dist/video-js.css';
|
import 'video.js/dist/video-js.css';
|
||||||
|
|
||||||
|
/** 微信消息 - 视频 */
|
||||||
defineOptions({ name: 'WxVideoPlayer' });
|
defineOptions({ name: 'WxVideoPlayer' });
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -22,7 +23,6 @@ function playVideo() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 微信消息 - 视频播放 -->
|
|
||||||
<div class="cursor-pointer" @click="playVideo()">
|
<div class="cursor-pointer" @click="playVideo()">
|
||||||
<!-- 提示 -->
|
<!-- 提示 -->
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { default as WxVoicePlayer } from './wx-voice-play.vue';
|
export { default as WxVoicePlayer } from './wx-voice-play.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { IconifyIcon } from '@vben/icons';
|
|||||||
import { Tag } from 'ant-design-vue';
|
import { Tag } from 'ant-design-vue';
|
||||||
import BenzAMRRecorder from 'benz-amr-recorder'; // 因为微信语音是 amr 格式,所以需要用到 amr 解码器:https://www.npmjs.com/package/benz-amr-recorder
|
import BenzAMRRecorder from 'benz-amr-recorder'; // 因为微信语音是 amr 格式,所以需要用到 amr 解码器:https://www.npmjs.com/package/benz-amr-recorder
|
||||||
|
|
||||||
|
/** 微信消息 - 语音 */
|
||||||
defineOptions({ name: 'WxVoicePlayer' });
|
defineOptions({ name: 'WxVoicePlayer' });
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -62,10 +63,10 @@ function amrStop() {
|
|||||||
playing.value = false;
|
playing.value = false;
|
||||||
amr.value.stop();
|
amr.value.stop();
|
||||||
}
|
}
|
||||||
|
// TODO 芋艿:下面样式有点问题
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 微信消息 - 语音播放 -->
|
|
||||||
<div class="wx-voice-div cursor-pointer" @click="playVoice">
|
<div class="wx-voice-div cursor-pointer" @click="playVoice">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { Button } from 'ant-design-vue';
|
|||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { WxVideoPlayer } from '#/views/mp/components';
|
import { WxVideoPlayer } from '#/views/mp/components';
|
||||||
|
|
||||||
// TODO @dylan:vue 组件名小写 + 中划线
|
// TODO @dylan:@hw:vue 组件名小写 + 中划线
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
list: any[];
|
list: any[];
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// 消息类型(Follow: 关注时回复;Message: 消息回复;Keyword: 关键词回复)
|
// 消息类型(Follow: 关注时回复;Message: 消息回复;Keyword: 关键词回复)
|
||||||
// 作为 tab.name,enum 的数字不能随意修改,与 api 参数相关
|
// 作为 tab.name,enum 的数字不能随意修改,与 api 参数相关
|
||||||
|
// TODO @hw:ele 相比 antd 多了,看看要不要统一下;
|
||||||
export enum MsgType {
|
export enum MsgType {
|
||||||
Follow = 1,
|
Follow = 1,
|
||||||
Keyword = 3,
|
Keyword = 3,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// 统一导出所有模块组件
|
|
||||||
|
|
||||||
export { default as WxAccountSelect } from './wx-account-select/wx-account-select.vue';
|
export { default as WxAccountSelect } from './wx-account-select/wx-account-select.vue';
|
||||||
|
|
||||||
export { default as WxLocation } from './wx-location/wx-location.vue';
|
export { default as WxLocation } from './wx-location/wx-location.vue';
|
||||||
@@ -20,3 +18,5 @@ export { default as WxReplySelect } from './wx-reply/wx-reply.vue';
|
|||||||
export { default as WxVideoPlayer } from './wx-video-play/wx-video-play.vue';
|
export { default as WxVideoPlayer } from './wx-video-play/wx-video-play.vue';
|
||||||
|
|
||||||
export { default as WxVoicePlayer } from './wx-voice-play/wx-voice-play.vue';
|
export { default as WxVoicePlayer } from './wx-voice-play/wx-voice-play.vue';
|
||||||
|
|
||||||
|
// TODO @hw:是不是要和 antd 保持一致哈?
|
||||||
@@ -1 +1,3 @@
|
|||||||
export { default } from './wx-account-select.vue';
|
export { default } from './wx-account-select.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ElMessage, ElOption, ElSelect } from 'element-plus';
|
|||||||
|
|
||||||
import { getSimpleAccountList } from '#/api/mp/account';
|
import { getSimpleAccountList } from '#/api/mp/account';
|
||||||
|
|
||||||
|
// TODO @hw:调整下代码,和 antd 代码风格,尽量保持一致;
|
||||||
defineOptions({ name: 'AccountSelect' });
|
defineOptions({ name: 'AccountSelect' });
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { default } from './wx-location.vue';
|
export { default as WxLocation } from './wx-location.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<!--
|
<!--
|
||||||
【微信消息 - 定位】TODO @Dhb52 目前未启用
|
【微信消息 - 定位】TODO @Dhb52 目前未启用;;;;@hw:看看目前是不是没用起来哈?
|
||||||
-->
|
-->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
// TODO @dylan:@hw:apps/web-antd/src/views/mall/trade/delivery/pickUpStore/modules/form.vue 参考这个,从后端拿 key 哈
|
||||||
import { ElCol, ElLink, ElRow } from 'element-plus';
|
import { ElCol, ElLink, ElRow } from 'element-plus';
|
||||||
|
|
||||||
defineOptions({ name: 'Location' });
|
defineOptions({ name: 'Location' });
|
||||||
|
|
||||||
|
// TODO @hw:antd 和 ele 这里的风格,看看怎么统一!
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
locationX: {
|
locationX: {
|
||||||
required: true,
|
required: true,
|
||||||
@@ -39,6 +41,7 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!-- 微信消息 - 定位 -->
|
||||||
<div>
|
<div>
|
||||||
<ElLink
|
<ElLink
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export { MaterialType, NewsType } from './types';
|
export { MaterialType, NewsType } from './types';
|
||||||
|
|
||||||
export { default } from './wx-material-select.vue';
|
export { default } from './wx-material-select.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO @hw:这里的枚举,看看要不要统一
|
||||||
export enum NewsType {
|
export enum NewsType {
|
||||||
Draft = '2',
|
Draft = '2',
|
||||||
Published = '1',
|
Published = '1',
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
<!--
|
|
||||||
- Copyright (C) 2018-2019
|
|
||||||
- All rights reserved, Designed By www.joolun.com
|
|
||||||
芋道源码:
|
|
||||||
-->
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
|
|
||||||
@@ -26,6 +21,9 @@ import VoicePlayer from '#/views/mp/components/wx-voice-play/wx-voice-play.vue';
|
|||||||
|
|
||||||
import { NewsType } from './types';
|
import { NewsType } from './types';
|
||||||
|
|
||||||
|
// TODO @hw:代码风格,看看 antd 和 ele 是不是统一下;
|
||||||
|
|
||||||
|
/** 微信素材选择 */
|
||||||
defineOptions({ name: 'MaterialSelect' });
|
defineOptions({ name: 'MaterialSelect' });
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -41,18 +39,14 @@ const props = withDefaults(
|
|||||||
|
|
||||||
const emit = defineEmits(['selectMaterial']);
|
const emit = defineEmits(['selectMaterial']);
|
||||||
|
|
||||||
// 遮罩层
|
const loading = ref(false); // 遮罩层
|
||||||
const loading = ref(false);
|
const total = ref(0); // 总条数
|
||||||
// 总条数
|
const list = ref<any[]>([]); // 数据列表
|
||||||
const total = ref(0);
|
|
||||||
// 数据列表
|
|
||||||
const list = ref<any[]>([]);
|
|
||||||
// 查询参数
|
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
accountId: props.accountId,
|
accountId: props.accountId,
|
||||||
});
|
}); // 查询参数
|
||||||
|
|
||||||
/** 选择素材 */
|
/** 选择素材 */
|
||||||
function selectMaterialFun(item: any) {
|
function selectMaterialFun(item: any) {
|
||||||
@@ -288,6 +282,8 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** TODO @dylan:@hw:看看有没适合 tindwind 的哈。 */
|
||||||
|
|
||||||
.waterfall {
|
.waterfall {
|
||||||
column-gap: 10px;
|
column-gap: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export { MsgType } from './types';
|
export { MsgType } from './types';
|
||||||
|
|
||||||
export { default } from './wx-msg.vue';
|
export { default } from './wx-msg.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const props = defineProps<{
|
|||||||
item: any;
|
item: any;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
// TODO @hw:看看用 antd 的风格,还是 ele 的风格,就是下面的 item。
|
||||||
const item = ref(props.item);
|
const item = ref(props.item);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import avatarWechat from '#/assets/imgs/wechat.png';
|
|||||||
import Msg from './wx-msg.vue';
|
import Msg from './wx-msg.vue';
|
||||||
|
|
||||||
// 确保 User 类型被识别为已使用
|
// 确保 User 类型被识别为已使用
|
||||||
|
// TODO @hw:是不是不用 PropsUser 哈?
|
||||||
type PropsUser = User;
|
type PropsUser = User;
|
||||||
|
|
||||||
defineOptions({ name: 'MsgList' });
|
defineOptions({ name: 'MsgList' });
|
||||||
@@ -18,15 +19,16 @@ const props = defineProps<{
|
|||||||
user: PropsUser;
|
user: PropsUser;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 使用常量对象替代枚举,避免 linter 误报
|
|
||||||
const SendFrom = {
|
const SendFrom = {
|
||||||
MpBot: 2,
|
MpBot: 2,
|
||||||
User: 1,
|
User: 1,
|
||||||
} as const;
|
} as const; // 发送来源
|
||||||
|
|
||||||
|
// TODO @hw:是不是用 SendFrom ,或者 number?
|
||||||
type SendFromType = (typeof SendFrom)[keyof typeof SendFrom];
|
type SendFromType = (typeof SendFrom)[keyof typeof SendFrom];
|
||||||
|
|
||||||
// 显式引用枚举成员供模板使用
|
// 显式引用枚举成员供模板使用
|
||||||
|
// TODO @hw:是不是用 SendFrom 就好啦?
|
||||||
const MpBotValue = SendFrom.MpBot;
|
const MpBotValue = SendFrom.MpBot;
|
||||||
const UserValue = SendFrom.User;
|
const UserValue = SendFrom.User;
|
||||||
|
|
||||||
@@ -72,6 +74,8 @@ const getNickname = (sendFrom: SendFromType) =>
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
|
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
|
||||||
|
/** TODO @dylan:@hw 看看有没适合 tindwind 的哈。 */
|
||||||
|
|
||||||
@import url('../comment.scss');
|
@import url('../comment.scss');
|
||||||
@import url('../card.scss');
|
@import url('../card.scss');
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO @hw:是不是放枚举里?
|
||||||
export enum MsgType {
|
export enum MsgType {
|
||||||
Event = 'event',
|
Event = 'event',
|
||||||
Image = 'image',
|
Image = 'image',
|
||||||
@@ -10,6 +11,7 @@ export enum MsgType {
|
|||||||
Voice = 'voice',
|
Voice = 'voice',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @hw:用 MpUserApi 里的 user 可以么?
|
||||||
export interface User {
|
export interface User {
|
||||||
nickname: string;
|
nickname: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import MsgEvent from './msg-event.vue';
|
|||||||
|
|
||||||
defineOptions({ name: 'Msg' });
|
defineOptions({ name: 'Msg' });
|
||||||
|
|
||||||
|
// TODO @hw:这个貌似和 antd 的差很多?
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: any;
|
item: any;
|
||||||
}>();
|
}>();
|
||||||
@@ -86,5 +88,3 @@ const item = ref<any>(props.item);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { default } from './wx-music.vue';
|
export { default } from './wx-music.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<!--
|
|
||||||
【微信消息 - 音乐】
|
|
||||||
-->
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
/** 微信消息 - 音乐 */
|
||||||
defineOptions({ name: 'Music' });
|
defineOptions({ name: 'Music' });
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -38,6 +36,7 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- TODO @hw:ElLink -->
|
||||||
<el-link
|
<el-link
|
||||||
type="success"
|
type="success"
|
||||||
:underline="false"
|
:underline="false"
|
||||||
@@ -62,6 +61,8 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
/** TODO @dylan:@hw:看看有没适合 tindwind 的哈。 */
|
||||||
|
|
||||||
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 card.scss */
|
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 card.scss */
|
||||||
@import url('../wx-msg/card.scss');
|
@import url('../wx-msg/card.scss');
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { default } from './wx-news.vue';
|
export { default as WxNews } from './wx-news.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ElImage } from 'element-plus';
|
import { ElImage } from 'element-plus';
|
||||||
|
|
||||||
|
/** 微信消息 - 图文 */
|
||||||
defineOptions({ name: 'WxNews' });
|
defineOptions({ name: 'WxNews' });
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -61,6 +62,8 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
/** TODO @dylan:@hw:看看有没适合 tindwind 的哈。 */
|
||||||
|
|
||||||
.news-home {
|
.news-home {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export { createEmptyReply, type Reply, ReplyType } from './types';
|
export { createEmptyReply, type Reply, ReplyType } from './types';
|
||||||
|
|
||||||
export { default } from './wx-reply.vue';
|
export { default } from './wx-reply.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -28,9 +28,11 @@ const emit = defineEmits<{
|
|||||||
(e: 'update:modelValue', v: Reply): void;
|
(e: 'update:modelValue', v: Reply): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
// TODO @hw:直接用 ElMessage
|
||||||
const message = ElMessage;
|
const message = ElMessage;
|
||||||
|
|
||||||
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
|
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
|
||||||
|
// TODO @hw:看看要不要和 antd 保持一致的风格;
|
||||||
const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
|
const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
|
||||||
const reply = computed<Reply>({
|
const reply = computed<Reply>({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
@@ -41,9 +43,9 @@ const showDialog = ref(false);
|
|||||||
const fileList = ref([]);
|
const fileList = ref([]);
|
||||||
const uploadData = reactive({
|
const uploadData = reactive({
|
||||||
accountId: reply.value.accountId,
|
accountId: reply.value.accountId,
|
||||||
type: 'image',
|
|
||||||
title: '',
|
|
||||||
introduction: '',
|
introduction: '',
|
||||||
|
title: '',
|
||||||
|
type: 'image',
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 图片上传前校验 */
|
/** 图片上传前校验 */
|
||||||
@@ -157,5 +159,3 @@ function selectMaterial(item: any) {
|
|||||||
</ElRow>
|
</ElRow>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { UploadRawFile } from 'element-plus';
|
import type { UploadRawFile } from 'element-plus';
|
||||||
|
|
||||||
|
// TODO @hw:类似 tab-image.vue 的建议
|
||||||
import type { Reply } from './types';
|
import type { Reply } from './types';
|
||||||
|
|
||||||
import { computed, reactive, ref } from 'vue';
|
import { computed, reactive, ref } from 'vue';
|
||||||
@@ -19,11 +20,8 @@ import {
|
|||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
|
||||||
import { UploadType, useBeforeUpload } from '#/utils/useUpload';
|
import { UploadType, useBeforeUpload } from '#/utils/useUpload';
|
||||||
// import { getAccessToken } from '@/utils/auth'
|
|
||||||
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
|
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
|
||||||
|
|
||||||
// 设置上传的请求头部
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: Reply;
|
modelValue: Reply;
|
||||||
}>();
|
}>();
|
||||||
@@ -45,9 +43,9 @@ const showDialog = ref(false);
|
|||||||
const fileList = ref([]);
|
const fileList = ref([]);
|
||||||
const uploadData = reactive({
|
const uploadData = reactive({
|
||||||
accountId: reply.value.accountId,
|
accountId: reply.value.accountId,
|
||||||
type: 'thumb', // 音乐类型为thumb
|
|
||||||
title: '',
|
|
||||||
introduction: '',
|
introduction: '',
|
||||||
|
title: '',
|
||||||
|
type: 'thumb', // 音乐类型为 thumb
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 图片上传前校验 */
|
/** 图片上传前校验 */
|
||||||
|
|||||||
@@ -12,13 +12,17 @@ import News from '#/views/mp/components/wx-news/wx-news.vue';
|
|||||||
|
|
||||||
import { NewsType } from '../wx-material-select/types';
|
import { NewsType } from '../wx-material-select/types';
|
||||||
|
|
||||||
|
defineOptions({ name: 'TabNews' });
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: Reply;
|
modelValue: Reply;
|
||||||
newsType: NewsType;
|
newsType: NewsType;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:modelValue', v: Reply): void;
|
(e: 'update:modelValue', v: Reply): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const reply = computed<Reply>({
|
const reply = computed<Reply>({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
set: (val) => emit('update:modelValue', val),
|
set: (val) => emit('update:modelValue', val),
|
||||||
@@ -84,5 +88,3 @@ function onDelete() {
|
|||||||
</ElRow>
|
</ElRow>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import { UploadType, useBeforeUpload } from '#/utils/useUpload';
|
|||||||
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
|
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
|
||||||
import VideoPlayer from '#/views/mp/components/wx-video-play/wx-video-play.vue';
|
import VideoPlayer from '#/views/mp/components/wx-video-play/wx-video-play.vue';
|
||||||
|
|
||||||
|
defineOptions({ name: 'TabVideo' });
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: Reply;
|
modelValue: Reply;
|
||||||
}>();
|
}>();
|
||||||
@@ -30,13 +32,16 @@ const emit = defineEmits<{
|
|||||||
(e: 'update:modelValue', v: Reply): void;
|
(e: 'update:modelValue', v: Reply): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
// TODO @hw:还是用 ElMessage
|
||||||
const message = ElMessage;
|
const message = ElMessage;
|
||||||
|
|
||||||
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
|
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
|
||||||
|
// TODO @hw:这里 antd 和 ele 有差异,要统一么?
|
||||||
const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
|
const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
|
||||||
|
|
||||||
const reply = computed<Reply>({
|
const reply = computed<Reply>({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
|
// TODO @hw:这里 antd 和 ele 有差异,要统一么?
|
||||||
set: (val: Reply) => emit('update:modelValue', val),
|
set: (val: Reply) => emit('update:modelValue', val),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -44,9 +49,9 @@ const showDialog = ref(false);
|
|||||||
const fileList = ref([]);
|
const fileList = ref([]);
|
||||||
const uploadData = reactive({
|
const uploadData = reactive({
|
||||||
accountId: reply.value.accountId,
|
accountId: reply.value.accountId,
|
||||||
type: 'video',
|
|
||||||
title: '',
|
|
||||||
introduction: '',
|
introduction: '',
|
||||||
|
title: '',
|
||||||
|
type: 'video',
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 视频上传前校验 */
|
/** 视频上传前校验 */
|
||||||
@@ -142,5 +147,3 @@ function selectMaterial(item: any) {
|
|||||||
</ElRow>
|
</ElRow>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { UploadType, useBeforeUpload } from '#/utils/useUpload';
|
|||||||
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
|
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
|
||||||
import VoicePlayer from '#/views/mp/components/wx-voice-play/wx-voice-play.vue';
|
import VoicePlayer from '#/views/mp/components/wx-voice-play/wx-voice-play.vue';
|
||||||
|
|
||||||
// 设置上传的请求头部
|
defineOptions({ name: 'TabVoice' });
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: Reply;
|
modelValue: Reply;
|
||||||
@@ -31,12 +31,15 @@ const emit = defineEmits<{
|
|||||||
(e: 'update:modelValue', v: Reply): void;
|
(e: 'update:modelValue', v: Reply): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
// TODO @hw:用 ElMessage
|
||||||
const message = ElMessage;
|
const message = ElMessage;
|
||||||
|
|
||||||
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
|
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
|
||||||
|
// TODO @hw:antd 和 ele 写法的统一;
|
||||||
const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
|
const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
|
||||||
const reply = computed<Reply>({
|
const reply = computed<Reply>({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
|
// TODO @hw:这里要和 antd 统一么?还是 ele 和它统一
|
||||||
set: (val: Reply) => emit('update:modelValue', val),
|
set: (val: Reply) => emit('update:modelValue', val),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -44,9 +47,9 @@ const showDialog = ref(false);
|
|||||||
const fileList = ref([]);
|
const fileList = ref([]);
|
||||||
const uploadData = reactive({
|
const uploadData = reactive({
|
||||||
accountId: reply.value.accountId,
|
accountId: reply.value.accountId,
|
||||||
type: 'voice',
|
|
||||||
title: '',
|
|
||||||
introduction: '',
|
introduction: '',
|
||||||
|
title: '',
|
||||||
|
type: 'voice',
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 语音上传前校验 */
|
/** 语音上传前校验 */
|
||||||
@@ -156,5 +159,3 @@ function selectMaterial(item: Reply) {
|
|||||||
</ElRow>
|
</ElRow>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { Ref } from 'vue';
|
|||||||
|
|
||||||
import { unref } from 'vue';
|
import { unref } from 'vue';
|
||||||
|
|
||||||
|
// TODO @hw:和 antd 风格,保持一致;
|
||||||
enum ReplyType {
|
enum ReplyType {
|
||||||
Image = 'image',
|
Image = 'image',
|
||||||
Music = 'music',
|
Music = 'music',
|
||||||
@@ -30,7 +31,7 @@ interface _Reply {
|
|||||||
|
|
||||||
type Reply = _Reply; // Partial<_Reply>
|
type Reply = _Reply; // Partial<_Reply>
|
||||||
|
|
||||||
/** 利用旧的reply[accountId, type]初始化新的Reply */
|
/** 利用旧的 reply[accountId, type] 初始化新的 Reply */
|
||||||
const createEmptyReply = (old: Ref<Reply> | Reply): Reply => {
|
const createEmptyReply = (old: Ref<Reply> | Reply): Reply => {
|
||||||
return {
|
return {
|
||||||
accountId: unref(old).accountId,
|
accountId: unref(old).accountId,
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
<!--
|
|
||||||
- Copyright (C) 2018-2019
|
|
||||||
- All rights reserved, Designed By www.joolun.com
|
|
||||||
芋道源码:
|
|
||||||
① 移除多余的 rep 为前缀的变量,让 message 消息更简单
|
|
||||||
② 代码优化,补充注释,提升阅读性
|
|
||||||
③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入
|
|
||||||
④ 支持发送【视频】消息时,支持新建视频
|
|
||||||
-->
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Reply } from './types';
|
import type { Reply } from './types';
|
||||||
|
|
||||||
@@ -25,7 +16,8 @@ import TabVideo from './tab-video.vue';
|
|||||||
import TabVoice from './tab-voice.vue';
|
import TabVoice from './tab-voice.vue';
|
||||||
import { createEmptyReply, ReplyType } from './types';
|
import { createEmptyReply, ReplyType } from './types';
|
||||||
|
|
||||||
defineOptions({ name: 'ReplySelect' });
|
/** 消息回复选择 */
|
||||||
|
defineOptions({ name: 'WxReplySelect' });
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -36,14 +28,15 @@ const props = withDefaults(
|
|||||||
newsType: () => NewsType.Published,
|
newsType: () => NewsType.Published,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:modelValue', v: Reply): void;
|
(e: 'update:modelValue', v: Reply): void;
|
||||||
}>();
|
}>();
|
||||||
// 提供一个默认的 Reply 对象,避免 undefined 导致的错误
|
|
||||||
const defaultReply: Reply = {
|
const defaultReply: Reply = {
|
||||||
accountId: -1,
|
accountId: -1,
|
||||||
type: ReplyType.Text,
|
type: ReplyType.Text,
|
||||||
};
|
}; // 提供一个默认的 Reply 对象,避免 undefined 导致的错误
|
||||||
|
|
||||||
const reply = computed<Reply>({
|
const reply = computed<Reply>({
|
||||||
get: () => props.modelValue || defaultReply,
|
get: () => props.modelValue || defaultReply,
|
||||||
@@ -113,5 +106,3 @@ defineExpose({
|
|||||||
</ElTabPane>
|
</ElTabPane>
|
||||||
</ElTabs>
|
</ElTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { default } from './wx-video-play.vue';
|
export { default } from './wx-video-play.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
<!--
|
|
||||||
- Copyright (C) 2018-2019
|
|
||||||
- All rights reserved, Designed By www.joolun.com
|
|
||||||
【微信消息 - 视频】
|
|
||||||
芋道源码:
|
|
||||||
① bug 修复:
|
|
||||||
1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容;
|
|
||||||
存在的问题:mediaId 有效期是 3 天,超过时间后无法播放
|
|
||||||
2)重构后的做法:后端接收到微信公众号的视频消息后,将视频消息的 media_id 的文件内容保存到文件服务器中,这样前端可以直接使用 URL 播放。
|
|
||||||
② 体验优化:弹窗关闭后,自动暂停视频的播放
|
|
||||||
|
|
||||||
-->
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
@@ -20,8 +8,10 @@ import { ElDialog } from 'element-plus';
|
|||||||
|
|
||||||
import 'video.js/dist/video-js.css';
|
import 'video.js/dist/video-js.css';
|
||||||
|
|
||||||
defineOptions({ name: 'VideoPlayer' });
|
/** 微信消息 - 视频 */
|
||||||
|
defineOptions({ name: 'WxVideoPlayer' });
|
||||||
|
|
||||||
|
// TODO @hw:antd 或者 ele,props 保持一致;
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -31,10 +21,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const dialogVideo = ref(false);
|
const dialogVideo = ref(false);
|
||||||
|
|
||||||
// const handleEvent = (log) => {
|
|
||||||
// console.log('Basic player event', log)
|
|
||||||
// }
|
|
||||||
|
|
||||||
const playVideo = () => {
|
const playVideo = () => {
|
||||||
dialogVideo.value = true;
|
dialogVideo.value = true;
|
||||||
};
|
};
|
||||||
@@ -61,6 +47,7 @@ const playVideo = () => {
|
|||||||
:width="800"
|
:width="800"
|
||||||
:playback-rates="[0.7, 1.0, 1.5, 2.0]"
|
:playback-rates="[0.7, 1.0, 1.5, 2.0]"
|
||||||
/>
|
/>
|
||||||
|
<!-- TODO @hw:删除掉? -->
|
||||||
<!-- 事件,暫時沒用
|
<!-- 事件,暫時沒用
|
||||||
@mounted="handleMounted"-->
|
@mounted="handleMounted"-->
|
||||||
<!-- @ready="handleEvent($event)"-->
|
<!-- @ready="handleEvent($event)"-->
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export { default } from './wx-voice-play.vue';
|
export { default } from './wx-voice-play.vue';
|
||||||
|
|
||||||
|
// TODO @hw:每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
<!--
|
|
||||||
- Copyright (C) 2018-2019
|
|
||||||
- All rights reserved, Designed By www.joolun.com
|
|
||||||
【微信消息 - 语音】
|
|
||||||
芋道源码:
|
|
||||||
① bug 修复:
|
|
||||||
1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容;
|
|
||||||
存在的问题:mediaId 有效期是 3 天,超过时间后无法播放
|
|
||||||
2)重构后的做法:后端接收到微信公众号的视频消息后,将视频消息的 media_id 的文件内容保存到文件服务器中,这样前端可以直接使用 URL 播放。
|
|
||||||
② 代码优化:将 props 中的 reply 调成为 data 中对应的属性,并补充相关注释
|
|
||||||
-->
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
@@ -17,8 +6,10 @@ import { IconifyIcon } from '@vben/icons';
|
|||||||
// 因为微信语音是 amr 格式,所以需要用到 amr 解码器:https://www.npmjs.com/package/benz-amr-recorder
|
// 因为微信语音是 amr 格式,所以需要用到 amr 解码器:https://www.npmjs.com/package/benz-amr-recorder
|
||||||
import BenzAMRRecorder from 'benz-amr-recorder';
|
import BenzAMRRecorder from 'benz-amr-recorder';
|
||||||
|
|
||||||
|
/** 微信消息 - 语音 */
|
||||||
defineOptions({ name: 'VoicePlayer' });
|
defineOptions({ name: 'VoicePlayer' });
|
||||||
|
|
||||||
|
// TODO @hw:antd 和 ele 代码风格一致;
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
url: {
|
url: {
|
||||||
type: String, // 语音地址,例如说:https://www.iocoder.cn/xxx.amr
|
type: String, // 语音地址,例如说:https://www.iocoder.cn/xxx.amr
|
||||||
@@ -92,6 +83,7 @@ const amrStop = () => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
/** TODO @dylan:看看有没适合 tindwind 的哈。 */
|
||||||
.wx-voice-div {
|
.wx-voice-div {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO @hw:合并到 api 里,参考 antd 的做法;
|
||||||
interface NewsItem {
|
interface NewsItem {
|
||||||
title: string;
|
title: string;
|
||||||
thumbMediaId: string;
|
thumbMediaId: string;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import MenuEditor from '#/views/mp/menu/modules/menu-editor.vue';
|
|||||||
import MenuPreviewer from '#/views/mp/menu/modules/menu-previewer.vue';
|
import MenuPreviewer from '#/views/mp/menu/modules/menu-previewer.vue';
|
||||||
|
|
||||||
// Assets for backgrounds
|
// Assets for backgrounds
|
||||||
|
// TODO @hw:是不是资源的地址,统一下;antd 和 ele,目录不同。建议按照 ele 的方法先;
|
||||||
import iphoneBackImg from './assets/iphone_backImg.png';
|
import iphoneBackImg from './assets/iphone_backImg.png';
|
||||||
import menuFootImg from './assets/menu_foot.png';
|
import menuFootImg from './assets/menu_foot.png';
|
||||||
import menuHeadImg from './assets/menu_head.png';
|
import menuHeadImg from './assets/menu_head.png';
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO @hw:合并到 types;
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
value: 'view',
|
value: 'view',
|
||||||
|
|||||||
930
pnpm-lock.yaml
generated
930
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user