ASP.NET 使用Request.Files获取<input type=file/>上传文件

ASP.NET文件上传
本文介绍在ASP.NET中通过Request.Files实现文件上传的方法,包括使用HTML控件和服务器端控件的不同方式,以及如何处理文件上传。
我们知道,对于 ASP.NET 的服务器端控件 FileUpload 来说,可以直接用 FileUpload1.PostedFile 这样的代码来获取上传文件框的文件。


但如果我们没有用 FileUpload,而直接用的 <input type="file" name="pic1" /> 这样的 HTML 控件,ASP.NET 又如何取这些内容呢?


用 Request.Files。


Request.Files.Count 客户端传了多少文件过来。此时不论我们使用的是服务端控件还是 HTML 控件,因为到了客户端都是一样的 <input type="file";另外,即使文件上传框中没有选择文件,都可能会当作上传了无内容的文件(视客户端浏览器)。
Request.Files[i].ContentLength 获取上传文件的大小,以字节为单位。
Request.Files[i].ContentType 获取客户端发送的文件的 MIME 内容类型。
Request.Files[i].FileName
Request.Files[i].InputStream
Request.Files[i].SaveAs(string filename)
上面的 Request.Files[i],也可以是 Request.Files[name]。


在类中使用?


在类中,我们这样用:


System.Web.HttpContext.Current.Request.Files


用request.files实现异文件上传(开放式上传)


一直以为只有file框,只有在使用了runat="server"后才可以实现上传文件的功能。
如:
   <form id="form1" method="post" runat="server">
       <input id="File1" type="file" name="File1"/>
       <input id="Submit1" type="submit" value="submit" />
   </form>


其实,这只是一个误区,在.net中file框也可以不使用runat="server"也可以实现上传文件的功能。如:
   <form id="form1" method="post" enctype="multipart/form-data" action="d.aspx">
       <input id="File1" type="file" name="File1"/>
       <input id="Submit1" type="submit" value="submit" />
   </form>


从代码可以看出区别。如果file框没有加runat="server",则form里一定要加上


enctype="multipart/form-data"这样才可以实现上传文件到服务器。


同时可以看到第一个form中使用了runat="server"。而第二个没有。其实使用了server和没有使用


runat="server"是有区别的.使用了runat="server"的form编译后,action必定是指向本身的网页。而没


有加runat="server"的form可以指向一个网页。这样就可以实现异文件上传------与asp的上传方法相似


,但是代码就简便许多。


异文件上传的处理代码如下:
d.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;


public partial class d : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.Files.Count>0)
        {
            HttpPostedFile f = Request.Files[0];
            f.SaveAs(Server.MapPath("002.jpg"));
        }
    }
}


结论:request.files可以实现文件上传的两种方法。并且文件上传的两种方法,在代码页上是没有什么


区别的,重点是在在设计器上。关闭式(runat=server)方法,file框一定要加上runat=server,form也


是一定要加上runat=server。开放式(submit form)则file框和form都不加runat=server,即写成纯html


代码!!!(所以在form中加入method=post enctype="multipart/form-data")。


使用表单文件域(input type=”file”)时,在PostBack中使用Request.Files获取不到文件。研究了半天,发现在input标签中使用runat=”server”后,是能够正常获取的。但是为了前端的元素ID不被修改,我尽可能不使用runat=”server”。


网上查阅了一下,最终让我找到了原因。用了高级货之后,忘本了呵呵。


要让form能够传递文件的话,必须要在form标签中加入enctype=”multipart/form-data”


<form enctype="multipart/form-data" ID="form1" runat="server">
      <input type="file" name="filename" />
</form>
在input 中使用了runat=”server”,那么ASP.NET会自动处理这些事情,而如果我们要自己DIY,就得记住这些啦!
以下是网站目前的webconfig文件 改怎么修改 <?xml version="1.0" encoding="UTF-8"?> <!-- For more information on how to configure your ASP.NET application, please visit http://go.microsoft.com/fwlink/?LinkId=152368 --> <configuration> <connectionStrings> <add name="ApplicationServices" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient" /> </connectionStrings> <appSettings configProtectionProvider="RsaProtectedConfigurationProvider"> <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns="http://www.w3.org/2001/04/xmlenc#"> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" /> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#"> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" /> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <KeyName>Rsa Key</KeyName> </KeyInfo> <CipherData> <CipherValue>G+DsdRtaxe0KW7q0OXT2A2TaUpeiZtkWsxvozK3E5gCFiP2u +nz7kdqzzC9be6ODsipCEoLTOKove4o1LZ34Vpgf7xUBBhZD15Zq8DdazCyYHnrn3TO5AMzjQ6I5f7NYspz69dw1kx6fI6yLHc8nwvXkxLq0aI1VZtTcc 525An4T7e6N4KOuNTnDQCUKO+EJoJh4W7tISZ37ygGQGzSH747CMGXWx9ZsVcNz+Jtc+ot +cGt9Gm/A6+b2NeQd3IgRe3qooXb6V0Jdi/2wONuPJ1FVWXfsnQRFD0imJxzFk86ChKKWK1c0SWMsrqZhUi0w4aTD26T +QL77Bg4qQsRGsQ==</CipherValue> </CipherData> </EncryptedKey> </KeyInfo> <CipherData> <CipherValue>O+M +/jfmUYbauWytN4pGYc55ElmhWj8SMkZUJmmwJ9Zzpm46To5tsbKWamxHvdqemCvIa9zUITppIFTuDdeejMVHHpPsXHUUkTk602Z3Uph94+nBSXVn0Agx DhHzgCEmRrv/1nFfffr6z6YleY1oSxjFDbyghteTiG5ksdw9bsbyLByvE6Kzx6D9lOwhN1U6m5nltBwFeYGJ +HKV5ayk3yKc9NkRf7nhzYzscKf9T8N74iNLNUddMiKojFSj4KZl+exXDj0vWG5emWBxwUStlx8ebXsxILBvHZUUE8xVmNWnue6MfGu369mB +ilqWmQivTsmPQIiW228rDPQtAfH74zAuQUftU1LlpcMeGQnCGc999FZQaBsScOwub0RUu2GB4OD562EzahxNGgzlRvt4ZiRcPDi32AVlHR5AKWn/ZpZi fZdQOUzvW/e8lrOSrJc0Yzl/piERRf4qmyYglnrC/U6+H8oJ0mcD6yfPtdoMLfLcuQpuMuvpaPrSbOPxQaPxfeq+eFc+ +q0GpTj0cFndrgdK0o7a0NGjKyrcuYIKgOG6nnL5E9IPUoVZp/yycJLSVb0QybgnKn0IBRVZZHt3p4wMMS9VyNlsLnl+DD7TaU=</CipherValue> </CipherData> </EncryptedData> </appSettings> <system.web> <httpRuntime enableVersionHeader="false" requestValidationMode="2.0" executionTimeout="500" maxRequestLength="409600" useFullyQualifiedRedirectUrl="false" minFreeThreads="8" minLocalRequestFreeThreads="4" appRequestQueueLimit="100" /> <customErrors allowNestedErrors="true" defaultRedirect="http://www.huadonghospital.com/index.html" mode="On" /> <compilation debug="false" targetFramework="4.0"> <assemblies> <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> </assemblies> </compilation> <authentication mode="Forms"> <forms loginUrl="~/Account/LogOn" timeout="2880" /> </authentication> <membership> <providers> <clear /> <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" /> </providers> </membership> <profile> <providers> <clear /> <add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" /> </providers> </profile> <roleManager enabled="false"> <providers> <clear /> <add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="/" /> <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" /> </providers> </roleManager> <pages validateRequest="false"> <namespaces> <add namespace="System.Web.Helpers" /> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Routing" /> <add namespace="System.Web.WebPages" /> </namespaces> </pages> <httpCookies httpOnlyCookies="true" /> <globalization requestEncoding="GB2312" responseEncoding="GB2312" uiCulture="zh-CN" culture="zh-CN" fileEncoding="GB2312" /> </system.web> <system.webServer> <validation validateIntegratedModeConfiguration="false" /> <staticContent> <clientCache cacheControlMode="DisableCache" /> </staticContent> <defaultDocument> <files> <clear /> <add value="index.html" /> </files> </defaultDocument> <httpProtocol> <customHeaders> <remove name="X-Powered-By" /> <add name="X-Frame-Options" value="SAMEORIGIN" /> </customHeaders> </httpProtocol> <httpErrors errorMode="DetailedLocalOnly"> <remove statusCode="404" /> <error statusCode="404" path="/index.html" responseMode="ExecuteURL" /> </httpErrors> <security> <requestFiltering> <verbs> <add verb="OPTIONS" allowed="false" /> <add verb="TRACE" allowed="false" /> </verbs> </requestFiltering> </security> <rewrite> <rules> <rule name="HTTP to HTTPS redirect" stopProcessing="true"> <match url="(.*)" /> <conditions> <add input="{HTTPS}" pattern="off" ignoreCase="true" /> </conditions> <action type="Redirect" redirectType="Found" url="https://{HTTP_HOST}/{R:1}" /> </rule> </rules> </rewrite> </system.webServer> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" /> <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
最新发布
11-06
只有web.config:内容如下哪里是提高画面质量的选项:<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- CAUTION! logging requires the ASP.NET IIS worker process to have write permission to the "log" folder. Among other options, you may grant the myrtille application pool ("IIS AppPool\MyrtilleAppPool") write permission to it (normally automatically set by the myrtille installer). --> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, Log4net" /> <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> <section name="Myrtille.Web.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </sectionGroup> </configSections> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-1.5.1.25624" newVersion="1.5.2.14234" /> </dependentAssembly> </assemblyBinding> </runtime> <log4net> <root> <level value="DEBUG" /> <appender-ref ref="LogFileAppender" /> </root> <appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="log\Myrtille.Web.log" /> <param name="AppendToFile" value="true" /> <rollingStyle value="Size" /> <maxSizeRollBackups value="10" /> <maximumFileSize value="10MB" /> <staticLogFileName value="true" /> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> </appender> </log4net> <system.diagnostics> <trace autoflush="true" indentsize="4"> <listeners> <add name="Log4netTraceListener" type="Myrtille.Log.Log4netTraceListener, Myrtille.Common"> <!-- trace level (Information, Warning or Error). CAUTION! Information level will hinder performance --> <filter type="Myrtille.Log.Log4netTraceFilter, Myrtille.Common" initializeData="Error" /> </add> <!-- disable output window traces. CAUTION! enabling the default output window will hinder performance --> <remove name="Default" /> </listeners> </trace> <!-- enable the lines below for WCF tracing (caution as it will hinder performance). Use SvcTraceViewer.exe as viewer --> <!-- <sources> <source name="System.ServiceModel" switchValue="Information, ActivityTracing" propagateActivity="true"> <listeners> <add name="xml" /> </listeners> </source> <source name="System.ServiceModel.MessageLogging"> <listeners> <add name="xml" /> </listeners> </source> </sources> <sharedListeners> <add initializeData="log\Myrtille.Web.svclog" type="System.Diagnostics.XmlWriterTraceListener" name="xml" /> </sharedListeners> --> </system.diagnostics> <system.web> <compilation defaultLanguage="C#" targetFramework="4.5" /> <!-- handle file upload/download up to 1GB. maxRequestLength is measured in kilobytes and executionTimeout in seconds --> <!-- handle querystrings up to 32KB (default 2KB). this can prove useful with large XHRs in HTML4 mode --> <httpRuntime requestValidationMode="4.5" targetFramework="4.5" maxRequestLength="1048576" maxQueryStringLength="32768" executionTimeout="360000" /> <pages enableViewState="false" validateRequest="false" enableEventValidation="false" clientIDMode="AutoID" /> <authorization> <allow users="*" /> </authorization> <authentication mode="None" /> <!-- CAUTION! cookieless="UseUri" allows multiple connections/tabs but, as the http session id is passed into the url, it also implies a risk of session spoofing to mitigate that risk, an authentication cookie is stored into the browser initiating the http session; any other client accessing the http session without it will be rejected please note that, even if the http session is cookieless, this mechanism requires cookies to be enabled client side the "cookieless=" attribute is really confusing because it's in fact the method used by the browser to send the http session id... and using a cookie is the standard way to do it if using myrtille within iframe(s) on a same page, cookieless="UseUri" must be used --> <sessionState mode="InProc" cookieless="UseUri" timeout="60" /> <!--<sessionState mode="InProc" cookieless="UseCookies" cookieName="AuthSecToken" timeout="60"/>--> <globalization requestEncoding="utf-8" responseEncoding="utf-8" culture="auto" uiCulture="auto" /> </system.web> <system.webServer> <defaultDocument> <files> <clear /> <add value="Default.aspx" /> </files> </defaultDocument> <urlCompression doDynamicCompression="false" /> <staticContent> <mimeMap fileExtension=".webp" mimeType="image/webp" /> </staticContent> <security> <requestFiltering> <!-- handle file upload/download up to 1GB. maxAllowedContentLength is measured in bytes --> <!-- handle querystrings up to 32KB (default 2KB). this can prove useful with large XHRs in HTML4 mode --> <requestLimits maxAllowedContentLength="1073741824" maxQueryString="32768" /> </requestFiltering> </security> <handlers> <add path="/handlers/SocketHandler.ashx" verb="*" name="SocketHandler" type="Myrtille.Web.SocketHandler" /> <add path="/handlers/AudioSocketHandler.ashx" verb="*" name="AudioSocketHandler" type="Myrtille.Web.AudioSocketHandler" /> <add path="/handlers/EventSourceHandler.ashx" verb="*" name="EventSourceHandler" type="Myrtille.Web.EventSourceHandler" /> <add path="/handlers/LongPollingHandler.ashx" verb="*" name="LongPollingHandler" type="Myrtille.Web.LongPollingHandler" /> </handlers> </system.webServer> <system.serviceModel> <client> <endpoint address="http://localhost:8080/Myrtille/RemoteSessionProcess" binding="wsDualHttpBinding" bindingConfiguration="wsDualHttpBindingCallback" contract="Myrtille.Services.Contracts.IRemoteSessionProcess" /> <endpoint address="http://localhost:8080/Myrtille/FileStorage" binding="basicHttpBinding" bindingConfiguration="basicHttpBindingFileStream" contract="Myrtille.Services.Contracts.IFileStorage" /> <endpoint address="http://localhost:8080/Myrtille/PrinterService" binding="basicHttpBinding" bindingConfiguration="basicHttpBindingFileStream" contract="Myrtille.Services.Contracts.IPrinterService" /> <endpoint address="http://localhost:8080/Myrtille/MFAAuthentication" binding="basicHttpBinding" contract="Myrtille.Services.Contracts.IMFAAuthentication" /> <endpoint address="http://localhost:8080/Myrtille/EnterpriseService" binding="basicHttpBinding" contract="Myrtille.Services.Contracts.IEnterpriseService" /> <endpoint address="http://localhost:8080/Myrtille/ApplicationPoolService" binding="basicHttpBinding" contract="Myrtille.Services.Contracts.IApplicationPoolService" /> </client> <bindings> <wsDualHttpBinding> <binding name="wsDualHttpBindingCallback" receiveTimeout="infinite" maxReceivedMessageSize="2147483647"> <security mode="None" /> <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" /> <reliableSession inactivityTimeout="infinite" /> </binding> </wsDualHttpBinding> <basicHttpBinding> <!-- buffer size: 64KB; max file size: 1GB --> <binding name="basicHttpBindingFileStream" transferMode="Streamed" messageEncoding="Mtom" maxBufferSize="65536" maxReceivedMessageSize="1073741824" closeTimeout="infinite" openTimeout="infinite" receiveTimeout="infinite" sendTimeout="infinite"> <security mode="None" /> </binding> </basicHttpBinding> </bindings> <!-- enable the lines below for WCF tracing (caution as it will hinder performance). Use SvcTraceViewer.exe as viewer --> <!-- <diagnostics> <messageLogging logEntireMessage="true" maxMessagesToLog="300" logMessagesAtServiceLevel="true" logMalformedMessages="true" logMessagesAtTransportLevel="true" /> </diagnostics> --> </system.serviceModel> <applicationSettings> <Myrtille.Web.Properties.Settings> <setting name="ConnectionServiceUrl" serializeAs="String"> <value>http://localhost:8008/MyrtilleAdmin/ConnectionService/</value> </setting> </Myrtille.Web.Properties.Settings> </applicationSettings> <appSettings> <!-- disable Visual Studio SignalR --> <add key="vs:EnableBrowserLink" value="false" /> <!-- disable Visual Studio Page Inspector --> <add key="PageInspector:ServerCodeMappingSupport" value="Disabled" /> <!-- providing access to the remote clipboard could be a security issue, it can be disabled below --> <add key="AllowRemoteClipboard" value="true" /> <!-- allow file transfer (requires a domain to be specified on connect) --> <add key="AllowFileTransfer" value="true" /> <!-- allow print download (through a pdf virtual printer). requires the "Myrtille PDF" printer to be installed --> <add key="AllowPrintDownload" value="true" /> <!-- allow remote session sharing (the session is broadcasted to all guests; read/write access is granted individually) --> <add key="AllowSessionSharing" value="true" /> <!-- allow audio plaback --> <add key="AllowAudioPlayback" value="true" /> <!-- share a session by url, with owner rights. set to false to disable and provide a session url spoofing protection. requires cookieless="UseUri" session state (system.web/sessionState section above) --> <!-- if a session is shared by url between different clients, each client is identified by a unique key instead of the http session id --> <add key="AllowShareSessionUrl" value="true" /> <!-- client ip protection --> <add key="ClientIPTracking" value="false" /> <!-- if the browser window/tab of the remote session owner is closed without disconnecting first, or if the connection is lost, delay (ms) before the remote session is disconnected by the gateway. value must be greater than "browserPulseInterval" defined into config.js (default 10 secs). 0 to disable --> <!-- this comes in addition but does not replace the session idle timeout, defined at the RDS level, which will disconnect the session after no user inputs are received for a given time, independently of Myrtille --> <add key="ClientIdleTimeout" value="0" /> <!-- audio buffer. improves the audio quality at the expense of a slight latency --> <add key="AudioBuffering" value="true" /> <!-- show or hide the toolbar (you can hide it if using your own UI) --> <add key="ToolbarEnabled" value="true" /> <!-- connect from a login page or url (you can disable it if using a connection API) --> <add key="LoginEnabled" value="true" /> <!-- if login is enabled, url of the login page (change it if using your own page or leave empty to use the default page) --> <add key="LoginUrl" value="" /> <!-- recycle the application pool when there is no active remote session; not applicable if using the enterprise mode --> <add key="IdleAppPoolRecycling" value="false" /> </appSettings> </configuration>
07-20
<template> <view class="container"> <view class="header"> <text class="header-title">在线投诉举报平台</text> <text class="header-subtitle">诚信是企业生存和发展的根本</text> </view> <!-- 欢迎页面 --> <view v-if="showWelcome" class="welcome-section"> <view class="welcome-image"> <image src="/static/OnlinComplaint.jpg" mode=""></image> </view> <view class="welcome-content"> <text class="welcome-text">欢迎举报</text> <text class="welcome-text indent">诚信是做人做事的根本,同样诚信的企业才能生存和发展。其亚的任何人,无论职务的高低,都应谨守廉洁并自觉接受各方监督。</text> <text class="welcome-text indent">任何单位和个人都有有权举报其亚员工的违法违纪行为。同时,我们将对您提供的信息严格保密,一经查实,将根据公司制度给予相应奖励。</text> <text class="welcome-text indent">您可以选择匿名举报,无需填写个人联系方式。</text> </view> <view class="accept-box" :class="{disabled: countdown > 0}" @tap="handleAccept" > <text>我已阅读并理解上述内容</text> <view class="timer"> {{ countdown }}s </view> </view> </view> <!-- 表单页面 --> <view v-else class="form-section"> <view class="section-title"> <uni-icons type="compose" size="24" color="#3498db"></uni-icons> <text>投诉信息</text> </view> <view class="form-row"> <view class="form-col"> <view class="form-group"> <label for="title" class="required">投诉标题</label> <input type="text" id="title" placeholder="请输入投诉标题" v-model="formData.title" :class="{error: errors.title}" @blur="validateField('title')" > <text v-if="errors.title" class="error-text">{{ errors.title }}</text> </view> </view> <view class="form-col"> <view class="form-group"> <label for="category" class="required">类别</label> <picker @change="bindPickerChange" :value="categoryIndex" :range="categories.map(item => item.label)" class="picker" @blur="validateField('category')" > <view class="picker-content" :class="{placeholder: !formData.category, error: errors.category}"> {{ formData.category || '请选择投诉类别' }} </view> </picker> <text v-if="errors.category" class="error-text">{{ errors.category }}</text> </view> </view> </view> <view class="form-row"> <view class="form-col"> <view class="form-group"> <label for="complaineeName" class="required">被投诉人姓名</label> <input type="text" id="complaineeName" placeholder="请输入被投诉人姓名" v-model="formData.complaineeName" :class="{error: errors.complaineeName}" @blur="validateField('complaineeName')" > <text v-if="errors.complaineeName" class="error-text">{{ errors.complaineeName }}</text> </view> </view> <view class="form-col"> <view class="form-group"> <label for="company" class="required">被投诉人公司</label> <input type="text" id="company" placeholder="请输入公司名称" v-model="formData.company" :class="{error: errors.company}" @blur="validateField('company')" > <text v-if="errors.company" class="error-text">{{ errors.company }}</text> </view> </view> </view> <view class="form-row"> <view class="form-col"> <view class="form-group"> <label for="department" class="required">被投诉人部门</label> <input type="text" id="department" placeholder="请输入部门名称" v-model="formData.department" :class="{error: errors.department}" @blur="validateField('department')" > <text v-if="errors.department" class="error-text">{{ errors.department }}</text> </view> </view> </view> <view class="form-group"> <label for="content" class="required">投诉内容</label> <textarea id="content" placeholder="请您如实详细填写投诉内容(时间、地点、事件、经过)" v-model="formData.content" :class="{error: errors.content}" @blur="validateField('content')" ></textarea> <view class="content-counter"> <text>{{ formData.content.length }}/2000</text> </view> <text v-if="errors.content" class="error-text">{{ errors.content }}</text> </view> <view class="section-title"> <uni-icons type="paperclip" size="24" color="#3498db"></uni-icons> <text>上传附件</text> </view> <view class="upload-area" @tap="chooseAndUploadFile" :class="{disabled: uploadDisabled}"> <view class="upload-icon"> <uni-icons type="cloud-upload" size="48" color="#3498db"></uni-icons> </view> <view class="upload-text">点击上传到此处</view> <view class="file-types">支持word文档、照片、MP3、MP4等格式,每次上传文件大小不超过10M,附件过大请发投诉渠道邮箱</view> <view v-if="uploading" class="upload-progress"> <uni-progress :percent="uploadProgress" stroke-width="48" activeColor="#3498db"></uni-progress> </view> </view> <view v-if="files.length > 0" class="file-list"> <view v-for="(file, index) in files" :key="index" class="file-item"> <uni-icons type="paperclip" size="18" color="#3498db"></uni-icons> <view class="file-info"> <text class="file-name">{{ file.name }}</text> <text class="file-size">({{ formatFileSize(file.size) }})</text> </view> <view class="delete-btn" @tap="removeFile(index)" >X <uni-icons type="close" size="16" color="#ffffff" ></uni-icons> </view> </view> </view> <view class="section-title"> <uni-icons type="person" size="24" color="#3498db"></uni-icons> <text>联系方式(选填,支持匿名)</text> </view> <view class="form-row"> <view class="form-col"> <view class="form-group"> <label for="name">姓名(选填)</label> <input type="text" id="name" placeholder="请输入您的姓名(选填)" v-model="formData.name" :class="{error: errors.name}" @blur="validateField('name')" > <text v-if="errors.name" class="error-text">{{ errors.name }}</text> </view> </view> <view class="form-col"> <view class="form-group"> <label for="phone">手机号(选填)</label> <input type="number" id="phone" placeholder="请输入您的手机号(选填)" v-model="formData.phone" :class="{error: errors.phone}" @blur="validateField('phone')" > <text v-if="errors.phone" class="error-text">{{ errors.phone }}</text> </view> </view> </view> <view class="form-row"> <view class="form-col"> <view class="form-group"> <label for="email">邮箱(选填)</label> <input type="email" id="email" placeholder="请输入您的邮箱(选填)" v-model="formData.email" :class="{error: errors.email}" @blur="validateField('email')" > <text v-if="errors.email" class="error-text">{{ errors.email }}</text> </view> </view> </view> <view class="privacy-note"> 您可以选择匿名举报,无需填写个人联系方式。如填写联系方式,我们会对您的个人信息及投诉内容进行严格保密,便于后续跟进调查。 </view> <button class="submit-btn" @tap="submitForm" :disabled="uploading || processing"> <template v-if="processing">处理中...</template> <template v-else>提交投诉</template> </button> </view> </view> </template> <script setup> import { ref, onMounted } from 'vue'; import { onShow } from '@dcloudio/uni-app'; // 存储openID的变量 const openId = ref(''); // 表单数据 const formData = ref({ title: '', category: '', // 存储显示的类别名称 categoryValue: '', // 存储实际提交的类别值 complaineeName: '', company: '', department: '', content: '', name: '', // 可选 phone: '', // 可选 email: '' // 可选 }); // 错误信息 const errors = ref({}); // 文件列表 - 存储上传成功后的文件信息 const files = ref([]); // 上传状态管理 const uploading = ref(false); const uploadProgress = ref(0); const uploadDisabled = ref(false); // 处理状态(用于获取OpenID和提交过程中) const processing = ref(false); // 类别选择(包含显示文本和对应的值) const categories = ref([ { label: '财务违规', value: '1' }, { label: '道德问题', value: '2' }, { label: '职场骚扰', value: '3' }, { label: '安全隐患', value: '4' } ]); const categoryIndex = ref(-1); // 欢迎页状态 const showWelcome = ref(true); const countdown = ref(5); // 处理接受按钮点击 const handleAccept = () => { if (countdown.value > 0) return; showWelcome.value = false; }; // 选择类别 - 同时记录显示文本和实际值 const bindPickerChange = (e) => { categoryIndex.value = e.detail.value; const selected = categories.value[e.detail.value]; formData.value.category = selected.label; // 显示用 formData.value.categoryValue = selected.value; // 实际提交用 validateField('category'); // 选择后立即验证 }; // 选择并上传文件 const chooseAndUploadFile = async () => { if (uploading.value) return; try { // 选择文件 const res = await uni.chooseFile({ count: 5 - files.value.length, // 限制总文件数不超过5个 type: 'all', extension: ['png', 'jpg', 'jpeg', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'mp3', 'mp4'], }); if (res.tempFiles && res.tempFiles.length > 0) { // 开始上传 uploadFiles(res.tempFiles); } } catch (err) { console.log('选择文件失败', err); uni.showToast({ title: '选择文件失败', icon: 'error', duration: 2000 }); } }; // 上传文件到服务器 const uploadFiles = async (tempFiles) => { uploading.value = true; uploadDisabled.value = true; uploadProgress.value = 0; // 显示加载弹窗(小圆圈) uni.showLoading({ title: '正在上传...', mask: true // 防止背景点击 }); try { // 计算总文件大小 const totalSize = tempFiles.reduce((sum, file) => sum + file.size, 0); let uploadedSize = 0; // 逐个上传文件 for (let i = 0; i < tempFiles.length; i++) { const file = tempFiles[i]; await new Promise((resolve, reject) => { uni.uploadFile({ url: '/api/file/upload', filePath: file.path, name: 'file', header: {}, timeout: 360000, // 1分钟超时 // 移除进度更新逻辑(不再需要) success: (res) => { try { const response = JSON.parse(res.data); if (res.statusCode === 200 && response.code === 200 && response.data) { uploadedSize += file.size; files.value.push({ name: response.data.name, size: file.size, url: response.data.url, id: Date.now() + i }); resolve(); } else { throw new Error(response.msg || `文件上传失败,错误代码: ${response.code}`); } } catch (err) { reject(err); } }, fail: (err) => { reject(new Error(`上传失败: ${err.errMsg}`)); } }); }); } // 文件上传成功后提示 uni.showToast({ title: '文件上传成功', icon: 'success' }); } catch (err) { console.error('文件上传失败', err); uni.showToast({ title: err.message || '文件上传失败', icon: 'error' }); } finally { // 关闭加载弹窗 uni.hideLoading(); uploading.value = false; uploadDisabled.value = false; uploadProgress.value = 0; } }; // 移除文件 const removeFile = (index) => { files.value.splice(index, 1); }; // 格式化文件大小 const formatFileSize = (bytes) => { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; // 单个字段验证 const validateField = (field) => { const value = formData.value[field]; let error = ''; switch(field) { case 'title': if (!value) error = '请输入投诉标题'; else if (value.length > 100) error = '标题长度不能超过100个字符'; break; case 'category': if (!value) error = '请选择投诉类别'; break; case 'complaineeName': if (!value) error = '请输入被投诉人姓名'; else if (value.length > 50) error = '姓名长度不能超过50个字符'; break; case 'company': if (!value) error = '请输入被投诉人公司'; else if (value.length > 100) error = '公司名称长度不能超过100个字符'; break; case 'department': if (!value) error = '请输入被投诉人部门'; else if (value.length > 100) error = '部门名称长度不能超过100个字符'; break; case 'content': if (!value) error = '请输入投诉内容'; else if (value.length < 1) error = '投诉内容不能为空'; break; case 'name': if (value && value.length > 50) error = '姓名长度不能超过50个字符'; break; case 'phone': const phoneRegex = /^1[3-9]\d{9}$/; if (value && value.trim() !== '' && !phoneRegex.test(value)) { error = '请输入有效的手机号码'; } break; case 'email': const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (value && value.trim() !== '' && !emailRegex.test(value)) { error = '请输入有效的邮箱地址'; } break; } errors.value[field] = error; return !error; }; // 表单验证 - 验证所有必填项和格式 const validateForm = () => { errors.value = {}; let isValid = true; // 验证所有字段 const allFields = ['title', 'category', 'complaineeName', 'company', 'department', 'content', 'name', 'phone', 'email']; allFields.forEach(field => { if (!validateField(field)) { isValid = false; } }); return isValid; }; // 提交表单 const submitForm = async () => { if (uploading.value || processing.value) { uni.showToast({ title: '操作处理中,请稍后', icon: 'none', duration: 2000 }); return; } // 先验证表单 const isValid = validateForm(); if (!isValid) { // 滚动到第一个错误字段 const firstErrorField = Object.keys(errors.value).find(key => errors.value[key]); if (firstErrorField) { const element = document.getElementById(firstErrorField); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'center' }); // 给错误字段添加闪烁效果 element.classList.add('error-highlight'); setTimeout(() => element.classList.remove('error-highlight'), 1000); } } const firstError = Object.values(errors.value).find(msg => msg); if (firstError) { uni.showToast({ title: firstError, icon: 'none', duration: 2000 }); } return; } processing.value = true; try { // 从本地缓存获取OpenID const openId = uni.getStorageSync('wechat_openid') || ''; // 准备提交的数据 const submitData = { title: formData.value.title, complaintType: formData.value.categoryValue, bcomplaintName: formData.value.complaineeName, bcomplaintCompany: formData.value.company, bcomplaintDept: formData.value.department, bcomplaintContent: formData.value.content, processingStatus: "1", fileUrl: files.value.map(file => file.url).join(','), complaintPerson: formData.value.name || '', phoneNumber: formData.value.phone || '', email: formData.value.email || '', wxUserId: openId // 将openID传入wxUserId字段 }; uni.showLoading({ title: '提交中...', mask: true }); // 提交表单数据到接口 await new Promise((resolve, reject) => { uni.request({ url: '/api/incorruptFront/addComplainTs', method: 'POST', data: submitData, header: { 'Content-Type': 'application/json' }, success: (res) => { if (res.statusCode === 200 && res.data.code === 200) { resolve(res.data); } else { reject(new Error(res.data.msg || '提交失败')); } }, fail: (err) => { reject(new Error(`网络错误: ${err.errMsg}`)); } }); }); uni.hideLoading(); uni.showToast({ title: '投诉信息已提交', icon: 'success', duration: 2000 }); // 2秒后返回首页 setTimeout(() => { uni.reLaunch({ url: '/pages/index/index' }); }, 2000); } catch (error) { console.error('提交表单失败', error); uni.hideLoading(); uni.showToast({ title: error.message || '提交失败,请重试', icon: 'error', duration: 2000 }); } finally { processing.value = false; } }; // 页面加载 onMounted(() => { // 启动倒计时 const timer = setInterval(() => { if (countdown.value > 0) { countdown.value--; } else { clearInterval(timer); } }, 1000); // 从缓存获取openID openId.value = uni.getStorageSync('wechat_openid') || ''; }); </script> <style lang="scss"> /* 基础样式保持不变 */ .container { display: flex; flex-direction: column; min-height: 100vh; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); padding: 20rpx; } .header { background: linear-gradient(135deg, #3498db 0%, #2c3e50 100%); color: white; text-align: center; padding: 40rpx 20rpx; border-radius: 0 0 30rpx 30rpx; margin-bottom: 20rpx; box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.15); } .header-title { display: block; font-size: 40rpx; font-weight: bold; margin-bottom: 15rpx; letter-spacing: 1rpx; } .header-subtitle { font-size: 28rpx; opacity: 0.9; } .welcome-section { padding: 30rpx; transition: all 0.5s ease; } .welcome-image { width: 100%; height: 300rpx; border-radius: 20rpx; overflow: hidden; box-shadow: 0 8rpx 25rpx rgba(0, 0, 0, 0.1); display: flex; justify-content: center; align-items: center; } .welcome-image image { width: 100%; height: 100%; object-fit: cover; } .welcome-content { background-color: #f8f9fa; border-radius: 20rpx; padding: 30rpx; line-height: 1.8; font-size: 28rpx; color: #333; box-shadow: inset 0 0 10rpx rgba(0, 0, 0, 0.05); margin-bottom: 30rpx; } .welcome-text { display: block; margin-bottom: 20rpx; } .accept-box { display: flex; justify-content: center; align-items: center; padding: 25rpx; background-color: #e3f2fd; border-radius: 20rpx; cursor: pointer; transition: all 0.3s ease; border: 2rpx solid #bbdefb; box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1); } .accept-box.disabled { background-color: #f5f5f5; cursor: not-allowed; opacity: 0.7; } .accept-box .timer { background-color: #e74c3c; color: white; width: 70rpx; height: 70rpx; border-radius: 50%; display: flex; justify-content: center; align-items: center; margin-left: 20rpx; font-weight: bold; font-size: 30rpx; } .form-section { padding: 30rpx; } .section-title { font-size: 34rpx; color: #2c3e50; margin-bottom: 30rpx; padding-bottom: 15rpx; border-bottom: 2rpx solid #3498db; display: flex; align-items: center; text { margin-left: 15rpx; font-weight: bold; } } .form-group { margin-bottom: 35rpx; position: relative; } .form-row { &.full-width { display: flex; flex-wrap: wrap; margin: 0 -15rpx; margin-bottom: 40rpx; } } .form-col { flex: 1; padding: 0 15rpx; min-width: 300rpx; } label { display: block; margin-bottom: 15rpx; font-weight: 500; color: #2c3e50; font-size: 30rpx; } label.required::after { content: "*"; color: #e74c3c; margin-left: 8rpx; } input, select, textarea, .picker-content { width: 100%; padding: 25rpx; border: 2rpx solid #e0e0e0; border-radius: 15rpx; font-size: 28rpx; transition: all 0.3s ease; background-color: #fff; } input:focus, select:focus, textarea:focus, .picker-content:active { border-color: #3498db; outline: none; box-shadow: 0 0 0 3rpx rgba(52, 152, 219, 0.2); } textarea { min-height: 200rpx; resize: vertical; } /* 新增的内容计数器样式 */ .content-counter { text-align: right; font-size: 24rpx; color: #999; margin-top: 10rpx; } .picker-content { display: flex; align-items: center; min-height: 80rpx; /* 确保选择器高度与输入框一致 */ &.placeholder { color: #999; } } .upload-area { border: 2rpx dashed #3498db; border-radius: 20rpx; padding: 40rpx 30rpx; text-align: center; background-color: #e3f2fd; cursor: pointer; transition: all 0.3s ease; margin-bottom: 30rpx; } .upload-area.disabled { background-color: #f5f5f5; border-color: #bdc3c7; cursor: not-allowed; } .upload-area:active:not(.disabled) { background-color: #bbdefb; transform: scale(0.98); } .upload-icon { margin-bottom: 20rpx; } .upload-text { color: #2c3e50; margin-bottom: 20rpx; font-size: 30rpx; font-weight: 500; } .file-types { color: #7f8c8d; font-size: 26rpx; margin-bottom: 20rpx; } .upload-progress { width: 80%; margin: 20rpx auto 0; } .progress-text { display: block; margin-top: 10rpx; font-size: 26rpx; color: #3498db; } .file-list { margin-top: 20rpx; margin-bottom: 30rpx; } .file-item { display: flex; align-items: center; padding: 20rpx; background-color: #f8f9fa; border-radius: 15rpx; margin-bottom: 15rpx; border-left: 4rpx solid #3498db; } /* 优化文件信息显示 */ .file-info { flex: 1; margin: 0 15rpx; min-width: 0; /* 允许容器收缩 */ } .file-name { display: block; font-size: 28rpx; color: #2c3e50; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; /* 不换行,超出部分显示省略号 */ } .file-size { display: block; color: #7f8c8d; font-size: 24rpx; margin-top: 5rpx; } .delete-btn { width: 40rpx; height: 40rpx; border-radius: 50%; background-color: #e74c3c; display: flex; justify-content: center; align-items: center; margin-left: 10rpx; flex-shrink: 0; /* 确保按钮不被挤压 */ transition: all 0.2s ease; color: #ffffff; } .delete-btn:active { background-color: #c0392b; transform: scale(0.9); } .error-text { display: block; color: #e74c3c; font-size: 26rpx; margin-top: 10rpx; } input.error, textarea.error, .picker-content.error { border-color: #e74c3c; animation: shake 0.5s ease; } /* 错误字段高亮动画 */ .error-highlight { animation: highlight 1s ease; } @keyframes shake { 0%, 100% { transform: translateX(0); } 20%, 60% { transform: translateX(-5rpx); } 40%, 80% { transform: translateX(5rpx); } } @keyframes highlight { 0%, 100% { box-shadow: none; } 50% { box-shadow: 0 0 0 4rpx rgba(231, 76, 60, 0.3); } } .privacy-note { background-color: #f8f9fa; border-radius: 15rpx; padding: 25rpx; margin-top: 30rpx; font-size: 26rpx; color: #7f8c8d; text-align: center; border: 1rpx solid #e9ecef; line-height: 1.6; } .submit-btn { background: linear-gradient(to right, #3498db, #2980b9); color: white; border: none; padding: 25rpx 0; width: 100%; border-radius: 50rpx; font-size: 32rpx; font-weight: 600; margin-top: 30rpx; box-shadow: 0 5rpx 20rpx rgba(52, 152, 219, 0.4); transition: all 0.3s ease; } .submit-btn:disabled { background: #bdc3c7; box-shadow: none; opacity: 0.7; } .submit-btn:active:not(:disabled) { transform: translateY(-3rpx); box-shadow: 0 8rpx 25rpx rgba(52, 152, 219, 0.6); } /* 响应式设计 */ @media (min-width: 768px) { .container { max-width: 750px; margin: 0 auto; padding: 40rpx; } .welcome-image { height: 350rpx; } } .welcome-text.indent{ text-indent: 2em; } </style> 上传附件报错:Request URL: http://www.wyqg.top/api/file/upload Request Method: POST Status Code: 400 Bad Request Remote Address: 47.109.65.73:80 Referrer Policy: strict-origin-when-cross-origin Connection: keep-alive Content-Length: 0 Date: Mon, 01 Sep 2025 10:00:25 GMT Server: nginx/1.26.3 Accept: */* Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: keep-alive Content-Length: 10485948 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryGFsvh0KKYAXIXI27 Host: www.wyqg.top Origin: http://www.wyqg.top Referer: http://www.wyqg.top/pages/OnlinComplaint/OnlinComplaint?openid=ohhFX6TzyGvbXFuIL0ZPjEIpsJOo User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1 wechatdevtools/1.06.2504030 MicroMessenger/8.0.5 Language/zh_CN webview/17566896873445375 webdebugger port/57256 token/84cac3393fe84eaa19bae7107a7c7659 file: (binary) 但是我用postman测试接口,用post方法 Body选择 form-data,键file文件,值上传了一个MP4文件测试没问题
09-02
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值