mirror of
https://github.com/EvolutionAPI/evolution-manager.git
synced 2025-07-13 15:14:49 -06:00
send message
This commit is contained in:
parent
ba19640c19
commit
8e8bd216d5
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "evolution-manager",
|
||||
"version": "0.2.11",
|
||||
"version": "0.2.12",
|
||||
"main": "dist",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
@ -28,6 +28,7 @@ import Rabbitmq from "./settings/Rabbitmq.vue";
|
||||
import Chatwoot from "./settings/Chatwoot.vue";
|
||||
import Typebot from "./settings/Typebot.vue";
|
||||
|
||||
import OpenSendMessage from "./message/OpenSendMessage.vue";
|
||||
import MyGroups from "./message/MyGroups.vue";
|
||||
import MyChats from "./message/MyChats.vue";
|
||||
import MyContacts from "./message/MyContacts.vue";
|
||||
@ -40,6 +41,7 @@ export default {
|
||||
Rabbitmq,
|
||||
Chatwoot,
|
||||
Typebot,
|
||||
OpenSendMessage,
|
||||
MyGroups,
|
||||
MyChats,
|
||||
HasWhatsapp,
|
||||
@ -65,7 +67,13 @@ export default {
|
||||
id: "message",
|
||||
icon: "mdi-message",
|
||||
title: "Mensagens",
|
||||
components: ["HasWhatsapp", "MyContacts", "MyGroups", "MyChats"],
|
||||
components: [
|
||||
"OpenSendMessage",
|
||||
"HasWhatsapp",
|
||||
"MyContacts",
|
||||
"MyGroups",
|
||||
"MyChats",
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
@ -63,7 +63,12 @@
|
||||
<v-icon v-else size="40">mdi-account-circle</v-icon>
|
||||
</v-avatar>
|
||||
<b>{{ item.pushName }}</b>
|
||||
<v-chip v-if="item.id === instance.instance.owner" size="x-small" label color="success">
|
||||
<v-chip
|
||||
v-if="item.id === instance.instance.owner"
|
||||
size="x-small"
|
||||
label
|
||||
color="success"
|
||||
>
|
||||
Instância
|
||||
</v-chip>
|
||||
</div>
|
||||
|
57
src/components/instance/message/OpenSendMessage.vue
Normal file
57
src/components/instance/message/OpenSendMessage.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<v-card variant="outlined" :loading="loading">
|
||||
<v-card-title
|
||||
class="d-flex align-center"
|
||||
@click="openModal"
|
||||
style="cursor: pointer"
|
||||
v-ripple
|
||||
>
|
||||
<v-icon start>mdi-message-text</v-icon>
|
||||
Mandar mensagem
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
size="small"
|
||||
icon
|
||||
:disabled="loading"
|
||||
variant="tonal"
|
||||
@click.stop="openModal"
|
||||
:style="{ transform: expanded ? 'rotate(180deg)' : '' }"
|
||||
>
|
||||
<v-icon>mdi-open-in-new</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
<SendMessage :instance="instance" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SendMessage from "@/components/modal/SendMessage.vue";
|
||||
|
||||
export default {
|
||||
name: "MyGroups",
|
||||
props: {
|
||||
instance: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
expanded: false,
|
||||
loading: false,
|
||||
error: false,
|
||||
}),
|
||||
methods: {
|
||||
openModal() {
|
||||
// emit event send-message
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("send-message", {
|
||||
detail: {},
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
components: { SendMessage },
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
267
src/components/modal/SendMessage.vue
Normal file
267
src/components/modal/SendMessage.vue
Normal file
@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<v-dialog v-model="dialog" max-width="600px">
|
||||
<v-card>
|
||||
<v-card-title>Mandar mensagem</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form v-model="valid">
|
||||
<v-autocomplete
|
||||
v-model="message.number"
|
||||
label="Para"
|
||||
:loading="loadingContacts"
|
||||
:items="contacts"
|
||||
v-model:search="search"
|
||||
>
|
||||
<template v-slot:no-data>
|
||||
<v-list-item
|
||||
v-if="search"
|
||||
@click="addAndSelect"
|
||||
:title="search"
|
||||
></v-list-item>
|
||||
<v-list-item v-else title="Sem contatos"></v-list-item>
|
||||
</template>
|
||||
<template v-slot:selection="{ item }">
|
||||
<div class="d-flex gap-1 align-center">
|
||||
<v-avatar size="30">
|
||||
<v-img
|
||||
height="30"
|
||||
width="30"
|
||||
v-if="item?.raw?.photo"
|
||||
:src="item?.raw?.photo"
|
||||
/>
|
||||
<v-icon size="30" v-else>
|
||||
mdi-{{ item?.raw?.isGroup ? "account-group" : "account" }}
|
||||
</v-icon>
|
||||
</v-avatar>
|
||||
|
||||
<span>
|
||||
{{ item.raw.title }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:item="{ props, item }">
|
||||
<v-list-item v-bind="props" :title="null">
|
||||
<div class="d-flex align-center gap-1">
|
||||
<v-avatar size="36">
|
||||
<v-img
|
||||
height="36"
|
||||
width="36"
|
||||
v-if="item?.raw?.photo"
|
||||
:src="item?.raw?.photo"
|
||||
/>
|
||||
<v-icon size="36" v-else>
|
||||
mdi-{{ item?.raw?.isGroup ? "account-group" : "account" }}
|
||||
</v-icon>
|
||||
</v-avatar>
|
||||
<div>
|
||||
<p>{{ item?.raw?.title }}</p>
|
||||
<p class="text-disabled font-8">{{ item?.raw?.value }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
|
||||
<v-textarea
|
||||
v-model="message.textMessage.text"
|
||||
label="Mensagem"
|
||||
outlined
|
||||
dense
|
||||
:rules="[
|
||||
(v) => !!v || 'Mensagem é obrigatória',
|
||||
(v) =>
|
||||
v.length <= 1024 ||
|
||||
'Mensagem deve ter no máximo 1024 caracteres',
|
||||
]"
|
||||
:counter="1024"
|
||||
:disabled="loading"
|
||||
rows="3"
|
||||
class="mb-3"
|
||||
></v-textarea>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<v-select
|
||||
v-model="message.options.presence"
|
||||
:items="[
|
||||
'composing',
|
||||
'available',
|
||||
'active',
|
||||
'unavailable',
|
||||
'paused',
|
||||
]"
|
||||
density="compact"
|
||||
label="Presença"
|
||||
:rules="[(v) => !!v || 'Presença é obrigatória']"
|
||||
:disabled="loading"
|
||||
class="mb-3"
|
||||
></v-select>
|
||||
<v-text-field
|
||||
v-model="message.options.delay"
|
||||
type="number"
|
||||
label="Delay"
|
||||
density="compact"
|
||||
:hint="`Delay em milisegundos
|
||||
(${(message.options.delay / 1000).toFixed(1)} segundos)`"
|
||||
:rules="[(v) => !!v || 'Delay é obrigatório']"
|
||||
:disabled="loading"
|
||||
class="mb-3"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-form>
|
||||
|
||||
<v-alert type="success" v-if="success">
|
||||
{{ success.message }} <b>{{ success.messageId }}</b>
|
||||
</v-alert>
|
||||
<v-alert type="error" v-if="error">
|
||||
{{ Array.isArray(error) ? error.join(", ") : error }}
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn text to="/" :disabled="loading">Cancel</v-btn>
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="tonal"
|
||||
@click="send"
|
||||
:disabled="!valid"
|
||||
:loading="loading"
|
||||
>
|
||||
Enviar
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import instanceController from "@/services/instanceController";
|
||||
import { useAppStore } from "@/store/app";
|
||||
import { mergeDeep } from "@/helpers/deepMerge";
|
||||
|
||||
const defaultMessage = (obj = {}) =>
|
||||
mergeDeep(
|
||||
{
|
||||
number: null,
|
||||
options: {
|
||||
delay: 1200,
|
||||
presence: "composing",
|
||||
},
|
||||
textMessage: {
|
||||
text: "",
|
||||
},
|
||||
},
|
||||
obj
|
||||
);
|
||||
|
||||
export default {
|
||||
name: "SettingsModal",
|
||||
data: () => ({
|
||||
dialog: false,
|
||||
valid: false,
|
||||
loading: false,
|
||||
loadingContacts: false,
|
||||
error: false,
|
||||
contacts: [],
|
||||
search: "",
|
||||
success: false,
|
||||
AppStore: useAppStore(),
|
||||
message: defaultMessage(),
|
||||
}),
|
||||
methods: {
|
||||
async send() {
|
||||
try {
|
||||
this.loading = true;
|
||||
this.success = false;
|
||||
this.error = false;
|
||||
|
||||
const response = await instanceController.chat.sendMessage(
|
||||
this.instance.instance.instanceName,
|
||||
this.message
|
||||
);
|
||||
|
||||
this.success = {
|
||||
messageId: response.key.id,
|
||||
message: "Mensagem enviada com sucesso",
|
||||
};
|
||||
this.message = defaultMessage();
|
||||
setTimeout(() => {
|
||||
this.success = false;
|
||||
}, 10000);
|
||||
} catch (e) {
|
||||
this.error = e.message?.message || e.message || e;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
open(obj) {
|
||||
this.message = defaultMessage(obj);
|
||||
this.dialog = true;
|
||||
if (!this.contacts.length) {
|
||||
this.loadContacts();
|
||||
}
|
||||
},
|
||||
addAndSelect() {
|
||||
this.contacts.push({
|
||||
title: this.search,
|
||||
value: this.search,
|
||||
photo: null,
|
||||
isGroup: false,
|
||||
});
|
||||
this.$nextTick(() => {
|
||||
this.message.number = this.search;
|
||||
});
|
||||
},
|
||||
async loadContacts() {
|
||||
try {
|
||||
this.loadingContacts = true;
|
||||
this.error = false;
|
||||
const contacts = await instanceController.chat.getContacts(
|
||||
this.instance.instance.instanceName
|
||||
);
|
||||
const groups = await instanceController.group.getAll(
|
||||
this.instance.instance.instanceName
|
||||
);
|
||||
|
||||
const groupsPhotos = {};
|
||||
|
||||
this.contacts = [
|
||||
...contacts
|
||||
.filter((c) => {
|
||||
const isGroup = c.id.indexOf("g.us") !== -1;
|
||||
if (isGroup) groupsPhotos[c.id] = c.profilePictureUrl;
|
||||
return !isGroup;
|
||||
})
|
||||
.map((c) => ({
|
||||
title: c.pushName || c.id,
|
||||
value: c.id,
|
||||
photo: c.profilePictureUrl,
|
||||
isGroup: false,
|
||||
})),
|
||||
...groups.map((g) => ({
|
||||
title: g.subject,
|
||||
value: g.id,
|
||||
photo: groupsPhotos[g.id],
|
||||
isGroup: true,
|
||||
})),
|
||||
];
|
||||
} catch (e) {
|
||||
this.error = e.message?.message || e.message || e;
|
||||
} finally {
|
||||
this.loadingContacts = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("send-message", (e) => {
|
||||
this.open(e.detail);
|
||||
});
|
||||
},
|
||||
props: {
|
||||
instance: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
emits: ["close"],
|
||||
};
|
||||
</script>
|
31
src/helpers/deepMerge.js
Normal file
31
src/helpers/deepMerge.js
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Simple object check.
|
||||
* @param item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isObject(item) {
|
||||
return (item && typeof item === 'object' && !Array.isArray(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep merge two objects.
|
||||
* @param target
|
||||
* @param ...sources
|
||||
*/
|
||||
export function mergeDeep(target, ...sources) {
|
||||
if (!sources.length) return target;
|
||||
const source = sources.shift();
|
||||
|
||||
if (isObject(target) && isObject(source)) {
|
||||
for (const key in source) {
|
||||
if (isObject(source[key])) {
|
||||
if (!target[key]) Object.assign(target, { [key]: {} });
|
||||
mergeDeep(target[key], source[key]);
|
||||
} else {
|
||||
Object.assign(target, { [key]: source[key] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mergeDeep(target, ...sources);
|
||||
}
|
@ -39,8 +39,23 @@ const getContacts = async (instanceName, numbers) => {
|
||||
});
|
||||
}
|
||||
|
||||
const sendMessage = async (instanceName, options) => {
|
||||
return await http
|
||||
.post("/message/sendText/:instance", options, {
|
||||
params: {
|
||||
instance: instanceName
|
||||
}
|
||||
})
|
||||
.then((r) => r.data)
|
||||
.catch((error) => {
|
||||
throw error.response?.data || error.response || error;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export default {
|
||||
getAll: getAll,
|
||||
hasWhatsapp: hasWhatsapp,
|
||||
getContacts: getContacts
|
||||
getContacts: getContacts,
|
||||
sendMessage: sendMessage
|
||||
}
|
Loading…
Reference in New Issue
Block a user