You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django项目中JavaScript导入外部库致代码失效问题求助

问题

我正在给Django项目的网站添加聊天功能,需求是每条消息容器后显示「多久之前」的时间提示。
遇到两个问题:

  • 导入非JavaScript内置的外部库(比如date-fns的formatDistanceToNow)后,代码完全失效;移除导入语句后,所有功能恢复正常。
  • 添加导入语句时,PyCharm编辑器会改变变量的颜色(例如从紫色变为白色)。

相关代码

import {formatDistanceToNow} from "date-fns";


/**
 * Variables
 */

let chatName = ''
let chatSocket = null
let chatWindowUrl = window.location.href
let chatRoomUuid = Math.random().toString(36).slice(2, 12)




/**
 * Elements
 */

const chatElement = document.querySelector('#chat')
const chatOpenElement = document.querySelector('#chat_open')
const chatJoinElement = document.querySelector('#chat_join')
const chatIconElement = document.querySelector('#chat_icon')
const chatWelcomeElement = document.querySelector('#chat_welcome')
const chatRoomElement = document.querySelector('#chat_room')
const chatNameElement = document.querySelector('#chat_name')
const chatLogElement = document.querySelector('#chat_log')
const chatInputElement = document.querySelector('#chat_message_input')
const chatSubmitElement = document.querySelector('#chat_message_submit')



/**
 * Functions
 */

function getCookie(name) {
    var cookieValue = null

    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';')

        for (var i = 0; i < cookies.length; i ++) {
            var cookie = cookies[i].trim()

            if (cookie.substring(0, name.length + 1) === (name + '=')){
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1))
                break
            }
        }
    }

    return cookieValue
}

function scrollToBottom() {
    // Scroll to the bottom of the chat log
    chatLogElement.scrollTop = chatLogElement.scrollHeight;
}
function formatTimeDifference(timeDifference) {
    const days = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
    const hours = Math.floor((timeDifference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
    const minutes = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60));

    if (days > 0) {
        return `${days} day${days > 1 ? 's' : ''} ago`;
    } else if (hours > 0) {
        return `${hours} hour${hours > 1 ? 's' : ''} ago`;
    } else {
        return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
    }
}

// Function to update the timestamp text
function updateTimestamps() {
    const timestamps = document.querySelectorAll('.timestamp');

    timestamps.forEach(timestamp => {
        const created_at = new Date(timestamp.dataset.createdat);

        // Calculate the time difference and format it using formatDistanceToNow
        const formattedTimeDifference = formatDistanceToNow(created_at);

        // Set the timestamp text with the updated time
        timestamp.textContent = formattedTimeDifference + " ago";
    });
}


function sendMessage() {
    chatSocket.send(JSON.stringify({
        'type': 'message',
        'message': chatInputElement.value,
        'name': chatName
    }))

    chatInputElement.value = ''
}
function onChatMessage(data) {
    console.log('onChatMessage', data);
    const message = document.getElementById("chat_message_input").value;

    if (data.type === 'chat_message') {
        // Create a container div for the chat message
        const container = document.createElement('div');
        container.classList.add('flex', 'w-full', 'mt-2', 'space-x-3', 'max-w-d'); // Apply CSS classes

        // Create the initials circle div
        const initialsDiv = document.createElement('div');
        initialsDiv.classList.add('flex-shrink-0', 'h-10', 'w-10', 'rounded-full', 'bg-gray-300', 'text-center', 'pt-2'); // Apply CSS classes
        initialsDiv.textContent = data.initials; // Set initials

        // Create the message container div
        const messageContainer = document.createElement('div');
        messageContainer.classList.add('message-container',); // Apply CSS classes

        // Create the message text paragraph
        const messageText = document.createElement('p');
        messageText.classList.add('message-text', 'text-sm'); // Apply CSS classes
        messageText.textContent = data.message; // Set message text

        // Create the timestamp span
        const timestampSpan = document.createElement('span');
        const created_at = new Date(data.created_at)
        const now = new Date()
        const timeDifference = now - created_at;


        const formattedTimeDifference = formatTimeDifference(timeDifference)

        timestampSpan.classList.add('timestamp'); // Apply CSS classes
        timestampSpan.textContent = formattedTimeDifference + ' ago'; // Set timestamp text

        // Append elements to the hierarchy
        messageContainer.appendChild(messageText);
        container.appendChild(initialsDiv);
        container.appendChild(messageContainer);


        const timestampWrapper = document.createElement('div');
        timestampWrapper.classList.add('timestamp-wrapper');
        timestampWrapper.appendChild(timestampSpan);

        // Append the dynamically created container to your chat log
        chatLogElement.appendChild(container);
        chatLogElement.appendChild(timestampWrapper);

        scrollToBottom()
        document.getElementById("chat_message_input").value = "";
    }
}



async function joinChatRoom() {
    console.log('joinChatRoom')

    chatName = chatNameElement.value

    console.log('Join as:', chatName)
    console.log('Room uuid:', chatRoomUuid)

    const data = new FormData()
    data.append('name', chatName)
    data.append('url', chatWindowUrl)

    await fetch(`/api/create-room/${chatRoomUuid}/`, {
        method: 'POST',
        headers: {
            'X-CSRFToken': getCookie('csrftoken')
        },
        body: data
    })
    .then(function(res) {
        return res.json()

    })
    .then(function (data){
        console.log('data', data)
    })

    chatSocket = new WebSocket(`ws://${window.location.host}/ws/${chatRoomUuid}/`)

    chatSocket.onmessage = function (e) {
        console.log('onMessage')

        onChatMessage(JSON.parse(e.data))
    }

    chatSocket.onopen = function (e) {
        console.log('onOpen - chat socket was opened')
    }

    chatSocket.onclose = function (e) {
        console.log('onClose - chat socket was closed')
    }
}


/**
 * Event Listeners
 */

chatOpenElement.onclick = function (e) {
    e.preventDefault()

    chatIconElement.classList.add('hidden')
    chatWelcomeElement.classList.remove('hidden')

    return false
}



chatJoinElement.onclick = function (e) {
    e.preventDefault()

    chatWelcomeElement.classList.add('hidden')
    chatRoomElement.classList.remove('hidden')

    joinChatRoom()

    return false
}


chatSubmitElement.onclick = function (e) {
    e.preventDefault()

    sendMessage()

    return false
}

document.getElementById("chat_message_input").addEventListener("keydown", function (event) {
    if (event.key === "Enter") {
        event.preventDefault(); // Prevent the Enter key from creating a new line
        // Call a function to send the message here
        sendMessage();
    }
});

setInterval(updateTimestamps, 60000); // Update every minute (adjust as needed)

解决方案

1. 代码失效的核心原因与修复

浏览器默认不支持在普通脚本文件中直接使用ES模块的import语法,这就是导入date-fns后代码崩溃的原因。有两种修复方式:

方式一:用CDN引入全局版date-fns

在HTML页面的<head>或<body>末尾添加date-fns的CDN脚本:

<script src="https://cdn.jsdelivr.net/npm/date-fns@2.30.0/index.min.js"></script>

然后修改JS代码:

  • 移除顶部的import语句
  • 在updateTimestamps函数里改用全局的dateFns对象调用方法:
    const formattedTimeDifference = dateFns.formatDistanceToNow(created_at);
    

注意:formatDistanceToNow本身会返回「5 minutes ago」这类带ago的字符串,要删掉代码里的+ " ago",避免重复。

方式二:将脚本声明为ES模块

在引入你的聊天JS文件的HTML标签中添加type="module":

<script type="module" src="/static/js/chat.js"></script>

这种方式需要先通过npm install date-fns安装依赖,且确保Django能正确处理静态资源的模块解析,纯静态部署场景更推荐CDN方式。

2. PyCharm变量颜色变化的问题

这是编辑器的正常行为:当识别到文件使用ES模块语法(import)时,PyCharm会切换到ES模块的作用域解析规则,变量高亮颜色会相应调整,不影响代码功能,解决导入问题后可忽略该现象。

额外建议

你已经自己实现了formatTimeDifference函数,如果不想引入外部库,完全可以继续使用这个函数,既避免了导入问题,也减少了外部依赖。

内容的提问来源于stack exchange,提问作者KNenov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 15:55:09