内容库优化 + 内容库导入功能
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import request from "@/api/request";
|
||||
|
||||
export function getContentLibraryList(params: any) {
|
||||
return request("/v1/content/library/list", params, "GET");
|
||||
return request("/v1/content/library/list", { ...params, formType: 0 }, "GET");
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export function getContentLibraryDetail(id: string): Promise<any> {
|
||||
export function createContentLibrary(
|
||||
params: CreateContentLibraryParams,
|
||||
): Promise<any> {
|
||||
return request("/v1/content/library/create", params, "POST");
|
||||
return request("/v1/content/library/create", { ...params, formType: 0 }, "POST");
|
||||
}
|
||||
|
||||
// 更新内容库
|
||||
|
||||
@@ -162,7 +162,7 @@ export default function ContentForm() {
|
||||
await updateContentLibrary({ id, ...payload });
|
||||
Toast.show({ content: "保存成功", position: "top" });
|
||||
} else {
|
||||
await request("/v1/content/library/create", payload, "POST");
|
||||
await request("/v1/content/library/create", { ...payload, formType: 0 }, "POST");
|
||||
Toast.show({ content: "创建成功", position: "top" });
|
||||
}
|
||||
navigate("/mine/content");
|
||||
|
||||
@@ -12,7 +12,7 @@ export function getContentLibraryList(params: {
|
||||
keyword?: string;
|
||||
sourceType?: number;
|
||||
}): Promise<any> {
|
||||
return request("/v1/content/library/list", params, "GET");
|
||||
return request("/v1/content/library/list", { ...params, formType: 0 }, "GET");
|
||||
}
|
||||
|
||||
// 获取内容库详情
|
||||
@@ -24,7 +24,7 @@ export function getContentLibraryDetail(id: string): Promise<any> {
|
||||
export function createContentLibrary(
|
||||
params: CreateContentLibraryParams,
|
||||
): Promise<any> {
|
||||
return request("/v1/content/library/create", params, "POST");
|
||||
return request("/v1/content/library/create", { ...params, formType: 0 }, "POST");
|
||||
}
|
||||
|
||||
// 更新内容库
|
||||
|
||||
@@ -47,3 +47,11 @@ export function aiRewriteContent(params: AIRewriteParams) {
|
||||
export function replaceContent(params: ReplaceContentParams) {
|
||||
return request("/v1/content/library/aiEditContent", params, "POST");
|
||||
}
|
||||
|
||||
// 导入Excel素材
|
||||
export function importMaterialsFromExcel(params: {
|
||||
id: string;
|
||||
fileUrl: string;
|
||||
}) {
|
||||
return request("/v1/content/library/import-excel", params, "POST");
|
||||
}
|
||||
|
||||
@@ -776,3 +776,87 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导入弹窗样式
|
||||
.import-popup-content {
|
||||
padding: 20px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
background: #ffffff;
|
||||
|
||||
.import-popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #e8f4ff;
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1677ff;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
background: #1677ff;
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.import-form {
|
||||
.import-form-item {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.import-form-label {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
background: #1677ff;
|
||||
margin-right: 6px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.import-form-control {
|
||||
.import-tip {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 8px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.import-actions {
|
||||
margin-top: 24px;
|
||||
|
||||
button {
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
|
||||
&:first-child {
|
||||
box-shadow: 0 2px 6px rgba(22, 119, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,12 @@ import {
|
||||
VideoCameraOutlined,
|
||||
FileTextOutlined,
|
||||
AppstoreOutlined,
|
||||
UploadOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import { getContentItemList, deleteContentItem, aiRewriteContent, replaceContent } from "./api";
|
||||
import { getContentItemList, deleteContentItem, aiRewriteContent, replaceContent, importMaterialsFromExcel } from "./api";
|
||||
import FileUpload from "@/components/Upload/FileUpload";
|
||||
import { ContentItem } from "./data";
|
||||
import style from "./index.module.scss";
|
||||
|
||||
@@ -50,6 +52,11 @@ const MaterialsList: React.FC = () => {
|
||||
const [aiLoading, setAiLoading] = useState(false);
|
||||
const [replaceLoading, setReplaceLoading] = useState(false);
|
||||
|
||||
// 导入相关状态
|
||||
const [showImportPopup, setShowImportPopup] = useState(false);
|
||||
const [importFileUrl, setImportFileUrl] = useState<string>("");
|
||||
const [importLoading, setImportLoading] = useState(false);
|
||||
|
||||
// 获取素材列表
|
||||
const fetchMaterials = useCallback(async () => {
|
||||
if (!id) return;
|
||||
@@ -187,6 +194,64 @@ const MaterialsList: React.FC = () => {
|
||||
fetchMaterials();
|
||||
};
|
||||
|
||||
// 处理导入文件上传
|
||||
const handleImportFileChange = (fileInfo: { fileName: string; fileUrl: string }) => {
|
||||
setImportFileUrl(fileInfo.fileUrl);
|
||||
};
|
||||
|
||||
// 执行导入
|
||||
const handleImport = async () => {
|
||||
if (!id) {
|
||||
Toast.show({
|
||||
content: "内容库ID不存在",
|
||||
position: "top",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!importFileUrl) {
|
||||
Toast.show({
|
||||
content: "请先上传Excel文件",
|
||||
position: "top",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setImportLoading(true);
|
||||
await importMaterialsFromExcel({
|
||||
id: id,
|
||||
fileUrl: importFileUrl,
|
||||
});
|
||||
|
||||
Toast.show({
|
||||
content: "导入成功",
|
||||
position: "top",
|
||||
});
|
||||
|
||||
// 关闭弹窗并重置状态
|
||||
setShowImportPopup(false);
|
||||
setImportFileUrl("");
|
||||
|
||||
// 刷新素材列表
|
||||
fetchMaterials();
|
||||
} catch (error: unknown) {
|
||||
console.error("导入失败:", error);
|
||||
Toast.show({
|
||||
content: error instanceof Error ? error.message : "导入失败,请重试",
|
||||
position: "top",
|
||||
});
|
||||
} finally {
|
||||
setImportLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭导入弹窗
|
||||
const closeImportPopup = () => {
|
||||
setShowImportPopup(false);
|
||||
setImportFileUrl("");
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
@@ -354,9 +419,18 @@ const MaterialsList: React.FC = () => {
|
||||
title="素材管理"
|
||||
backFn={() => navigate("/mine/content")}
|
||||
right={
|
||||
<>
|
||||
<Button
|
||||
type="default"
|
||||
onClick={() => setShowImportPopup(true)}
|
||||
style={{ marginRight: 8 }}
|
||||
>
|
||||
<UploadOutlined /> 导入
|
||||
</Button>
|
||||
<Button type="primary" onClick={handleCreateNew}>
|
||||
<PlusOutlined /> 新建素材
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{/* 搜索栏 */}
|
||||
@@ -586,6 +660,71 @@ const MaterialsList: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
|
||||
{/* 导入弹窗 */}
|
||||
<Popup
|
||||
visible={showImportPopup}
|
||||
onMaskClick={closeImportPopup}
|
||||
bodyStyle={{
|
||||
borderRadius: "16px 16px 0 0",
|
||||
maxHeight: "90vh",
|
||||
}}
|
||||
>
|
||||
<div className={style["import-popup-content"]}>
|
||||
<div className={style["import-popup-header"]}>
|
||||
<h3>导入素材</h3>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={closeImportPopup}
|
||||
>
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={style["import-form"]}>
|
||||
<div className={style["import-form-item"]}>
|
||||
<div className={style["import-form-label"]}>选择Excel文件</div>
|
||||
<div className={style["import-form-control"]}>
|
||||
<FileUpload
|
||||
value={importFileUrl}
|
||||
onChange={(url) => {
|
||||
const fileUrl = Array.isArray(url) ? url[0] : url;
|
||||
setImportFileUrl(fileUrl || "");
|
||||
}}
|
||||
acceptTypes={["excel"]}
|
||||
maxSize={50}
|
||||
maxCount={1}
|
||||
showPreview={false}
|
||||
/>
|
||||
<div className={style["import-tip"]}>
|
||||
请上传Excel格式的文件,文件大小不超过50MB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style["import-actions"]}>
|
||||
<Button
|
||||
block
|
||||
color="primary"
|
||||
onClick={handleImport}
|
||||
loading={importLoading}
|
||||
disabled={importLoading || !importFileUrl}
|
||||
>
|
||||
{importLoading ? "导入中..." : "确认导入"}
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
color="danger"
|
||||
fill="outline"
|
||||
onClick={closeImportPopup}
|
||||
style={{ marginTop: 12 }}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,6 +15,8 @@ use think\facade\Cache;
|
||||
use think\facade\Env;
|
||||
use app\api\controller\AutomaticAssign;
|
||||
use think\facade\Request;
|
||||
use PHPExcel_IOFactory;
|
||||
use app\common\util\AliyunOSS;
|
||||
|
||||
/**
|
||||
* 内容库控制器
|
||||
@@ -92,6 +94,8 @@ class ContentLibraryController extends Controller
|
||||
'timeEnd' => isset($param['endTime']) ? strtotime($param['endTime']) : 0, // 结束时间(转换为时间戳)
|
||||
// 来源类型
|
||||
'sourceType' => $sourceType, // 1=好友,2=群,3=好友和群
|
||||
// 表单类型
|
||||
'formType' => isset($param['formType']) ? intval($param['formType']) : 0, // 表单类型,默认为0
|
||||
// 基础信息
|
||||
'status' => isset($param['status']) ? $param['status'] : 0, // 状态:0=禁用,1=启用
|
||||
'userId' => $this->request->userInfo['id'],
|
||||
@@ -127,6 +131,7 @@ class ContentLibraryController extends Controller
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$sourceType = $this->request->param('sourceType', ''); // 来源类型,1=好友,2=群
|
||||
$formType = $this->request->param('formType', ''); // 表单类型筛选
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
$userId = $this->request->userInfo['id'];
|
||||
$isAdmin = !empty($this->request->userInfo['isAdmin']);
|
||||
@@ -152,12 +157,17 @@ class ContentLibraryController extends Controller
|
||||
$where[] = ['sourceType', '=', $sourceType];
|
||||
}
|
||||
|
||||
// 添加表单类型筛选
|
||||
if ($formType !== '') {
|
||||
$where[] = ['formType', '=', $formType];
|
||||
}
|
||||
|
||||
// 获取总记录数
|
||||
$total = ContentLibrary::where($where)->count();
|
||||
|
||||
// 获取分页数据
|
||||
$list = ContentLibrary::where($where)
|
||||
->field('id,name,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,sourceType,userId,createTime,updateTime')
|
||||
->field('id,name,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,sourceType,formType,userId,createTime,updateTime')
|
||||
->with(['user' => function ($query) {
|
||||
$query->field('id,username');
|
||||
}])
|
||||
@@ -319,7 +329,7 @@ class ContentLibraryController extends Controller
|
||||
|
||||
// 查询内容库信息
|
||||
$library = ContentLibrary::where($where)
|
||||
->field('id,name,sourceType,devices ,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,userId,companyId,createTime,updateTime,groupMembers,catchType')
|
||||
->field('id,name,sourceType,formType,devices ,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,userId,companyId,createTime,updateTime,groupMembers,catchType')
|
||||
->find();
|
||||
|
||||
if (empty($library)) {
|
||||
@@ -470,6 +480,7 @@ class ContentLibraryController extends Controller
|
||||
$library->timeEnabled = isset($param['timeEnabled']) ? $param['timeEnabled'] : 0;
|
||||
$library->timeStart = isset($param['startTime']) ? strtotime($param['startTime']) : 0;
|
||||
$library->timeEnd = isset($param['endTime']) ? strtotime($param['endTime']) : 0;
|
||||
$library->formType = isset($param['formType']) ? intval($param['formType']) : $library->formType; // 表单类型,如果未传则保持原值
|
||||
$library->status = isset($param['status']) ? $param['status'] : 0;
|
||||
$library->updateTime = time();
|
||||
$library->save();
|
||||
@@ -2384,4 +2395,352 @@ class ContentLibraryController extends Controller
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入Excel表格(支持图片导入)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function importExcel()
|
||||
{
|
||||
try {
|
||||
$libraryId = $this->request->param('id', 0);
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
$userId = $this->request->userInfo['id'];
|
||||
$isAdmin = !empty($this->request->userInfo['isAdmin']);
|
||||
|
||||
if (empty($libraryId)) {
|
||||
return json(['code' => 400, 'msg' => '内容库ID不能为空']);
|
||||
}
|
||||
|
||||
// 验证内容库权限
|
||||
$libraryWhere = [
|
||||
['id', '=', $libraryId],
|
||||
['companyId', '=', $companyId],
|
||||
['isDel', '=', 0]
|
||||
];
|
||||
|
||||
if (!$isAdmin) {
|
||||
$libraryWhere[] = ['userId', '=', $userId];
|
||||
}
|
||||
|
||||
$library = ContentLibrary::where($libraryWhere)->find();
|
||||
if (empty($library)) {
|
||||
return json(['code' => 500, 'msg' => '内容库不存在或无权限访问']);
|
||||
}
|
||||
|
||||
// 获取文件(可能是上传的文件或远程URL)
|
||||
$fileUrl = $this->request->param('fileUrl', '');
|
||||
$file = Request::file('file');
|
||||
$tmpFile = '';
|
||||
|
||||
if (!empty($fileUrl)) {
|
||||
// 处理远程URL
|
||||
if (!preg_match('/^https?:\/\//i', $fileUrl)) {
|
||||
return json(['code' => 400, 'msg' => '无效的文件URL']);
|
||||
}
|
||||
|
||||
// 验证文件扩展名
|
||||
$urlExt = strtolower(pathinfo(parse_url($fileUrl, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
if (!in_array($urlExt, ['xls', 'xlsx'])) {
|
||||
return json(['code' => 400, 'msg' => '只支持Excel文件(.xls, .xlsx)']);
|
||||
}
|
||||
|
||||
// 下载远程文件到临时目录
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'excel_import_') . '.' . $urlExt;
|
||||
$fileContent = @file_get_contents($fileUrl);
|
||||
|
||||
if ($fileContent === false) {
|
||||
return json(['code' => 400, 'msg' => '下载远程文件失败,请检查URL是否可访问']);
|
||||
}
|
||||
|
||||
file_put_contents($tmpFile, $fileContent);
|
||||
|
||||
} elseif ($file) {
|
||||
// 处理上传的文件
|
||||
$ext = strtolower($file->getExtension());
|
||||
if (!in_array($ext, ['xls', 'xlsx'])) {
|
||||
return json(['code' => 400, 'msg' => '只支持Excel文件(.xls, .xlsx)']);
|
||||
}
|
||||
|
||||
// 保存临时文件
|
||||
$tmpFile = $file->getRealPath();
|
||||
if (empty($tmpFile)) {
|
||||
$savePath = $file->move(sys_get_temp_dir());
|
||||
$tmpFile = $savePath->getRealPath();
|
||||
}
|
||||
} else {
|
||||
return json(['code' => 400, 'msg' => '请上传Excel文件或提供文件URL']);
|
||||
}
|
||||
|
||||
if (empty($tmpFile) || !file_exists($tmpFile)) {
|
||||
return json(['code' => 400, 'msg' => '文件不存在或无法访问']);
|
||||
}
|
||||
|
||||
// 加载Excel文件
|
||||
$excel = PHPExcel_IOFactory::load($tmpFile);
|
||||
$sheet = $excel->getActiveSheet();
|
||||
|
||||
// 获取所有图片
|
||||
$images = [];
|
||||
try {
|
||||
$drawings = $sheet->getDrawingCollection();
|
||||
foreach ($drawings as $drawing) {
|
||||
if ($drawing instanceof \PHPExcel_Worksheet_Drawing) {
|
||||
$coordinates = $drawing->getCoordinates();
|
||||
$imagePath = $drawing->getPath();
|
||||
|
||||
// 如果是嵌入的图片(zip://格式),提取到临时文件
|
||||
if (strpos($imagePath, 'zip://') === 0) {
|
||||
$zipEntry = str_replace('zip://', '', $imagePath);
|
||||
$zipEntry = explode('#', $zipEntry);
|
||||
$zipFile = $zipEntry[0];
|
||||
$imageEntry = isset($zipEntry[1]) ? $zipEntry[1] : '';
|
||||
|
||||
if (!empty($imageEntry)) {
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($zipFile) === true) {
|
||||
$imageContent = $zip->getFromName($imageEntry);
|
||||
if ($imageContent !== false) {
|
||||
$tempImageFile = tempnam(sys_get_temp_dir(), 'excel_img_');
|
||||
file_put_contents($tempImageFile, $imageContent);
|
||||
$images[$coordinates] = $tempImageFile;
|
||||
}
|
||||
$zip->close();
|
||||
}
|
||||
}
|
||||
} elseif (file_exists($imagePath)) {
|
||||
// 如果是外部文件路径
|
||||
$images[$coordinates] = $imagePath;
|
||||
}
|
||||
} elseif ($drawing instanceof \PHPExcel_Worksheet_MemoryDrawing) {
|
||||
// 处理内存中的图片
|
||||
$coordinates = $drawing->getCoordinates();
|
||||
$imageResource = $drawing->getImageResource();
|
||||
|
||||
if ($imageResource) {
|
||||
$tempImageFile = tempnam(sys_get_temp_dir(), 'excel_img_') . '.png';
|
||||
$imageType = $drawing->getMimeType();
|
||||
|
||||
switch ($imageType) {
|
||||
case 'image/png':
|
||||
imagepng($imageResource, $tempImageFile);
|
||||
break;
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
imagejpeg($imageResource, $tempImageFile);
|
||||
break;
|
||||
case 'image/gif':
|
||||
imagegif($imageResource, $tempImageFile);
|
||||
break;
|
||||
default:
|
||||
imagepng($imageResource, $tempImageFile);
|
||||
}
|
||||
|
||||
$images[$coordinates] = $tempImageFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\think\facade\Log::error('提取Excel图片失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
// 读取数据(实际内容从第三行开始,前两行是标题和说明)
|
||||
$data = $sheet->toArray();
|
||||
if (count($data) < 3) {
|
||||
return json(['code' => 400, 'msg' => 'Excel文件数据为空']);
|
||||
}
|
||||
|
||||
// 移除前两行(标题行和说明行)
|
||||
array_shift($data); // 移除第1行
|
||||
array_shift($data); // 移除第2行
|
||||
|
||||
$successCount = 0;
|
||||
$failCount = 0;
|
||||
$errors = [];
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
foreach ($data as $rowIndex => $row) {
|
||||
$rowNum = $rowIndex + 3; // Excel行号(从3开始,因为前两行是标题和说明)
|
||||
|
||||
// 跳过空行
|
||||
if (empty(array_filter($row))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// 解析数据(根据图片中的表格结构)
|
||||
// A:日期, B:投放时间, C:作用分类, D:朋友圈文案, E:自回评内容, F:朋友圈展示形式, G-O:配图1-9
|
||||
$date = isset($row[0]) ? trim($row[0]) : '';
|
||||
$placementTime = isset($row[1]) ? trim($row[1]) : '';
|
||||
$functionCategory = isset($row[2]) ? trim($row[2]) : '';
|
||||
$content = isset($row[3]) ? trim($row[3]) : '';
|
||||
$selfReply = isset($row[4]) ? trim($row[4]) : '';
|
||||
$displayForm = isset($row[5]) ? trim($row[5]) : '';
|
||||
|
||||
// 如果没有朋友圈文案,跳过
|
||||
if (empty($content)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 提取配图(G-O列,索引6-14)
|
||||
$imageUrls = [];
|
||||
for ($colIndex = 6; $colIndex <= 14; $colIndex++) {
|
||||
$columnLetter = $this->columnLetter($colIndex);
|
||||
$cellCoordinate = $columnLetter . $rowNum;
|
||||
|
||||
// 检查是否有图片
|
||||
if (isset($images[$cellCoordinate])) {
|
||||
$imagePath = $images[$cellCoordinate];
|
||||
|
||||
// 上传图片到OSS
|
||||
$imageExt = 'jpg';
|
||||
if (file_exists($imagePath)) {
|
||||
$imageInfo = @getimagesize($imagePath);
|
||||
if ($imageInfo) {
|
||||
$imageExt = image_type_to_extension($imageInfo[2], false);
|
||||
if ($imageExt === 'jpeg') {
|
||||
$imageExt = 'jpg';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$objectName = AliyunOSS::generateObjectName('excel_img_' . $rowNum . '_' . ($colIndex - 5) . '.' . $imageExt);
|
||||
$uploadResult = AliyunOSS::uploadFile($imagePath, $objectName);
|
||||
|
||||
if ($uploadResult['success']) {
|
||||
$imageUrls[] = $uploadResult['url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析日期和时间
|
||||
$createMomentTime = 0;
|
||||
if (!empty($date)) {
|
||||
// 尝试解析日期格式:2025年11月25日 或 2025-11-25
|
||||
$dateStr = $date;
|
||||
if (preg_match('/(\d{4})[年\-](\d{1,2})[月\-](\d{1,2})/', $dateStr, $matches)) {
|
||||
$year = $matches[1];
|
||||
$month = str_pad($matches[2], 2, '0', STR_PAD_LEFT);
|
||||
$day = str_pad($matches[3], 2, '0', STR_PAD_LEFT);
|
||||
|
||||
// 解析时间
|
||||
$hour = 0;
|
||||
$minute = 0;
|
||||
if (!empty($placementTime) && preg_match('/(\d{1,2}):(\d{2})/', $placementTime, $timeMatches)) {
|
||||
$hour = intval($timeMatches[1]);
|
||||
$minute = intval($timeMatches[2]);
|
||||
}
|
||||
|
||||
$createMomentTime = strtotime("{$year}-{$month}-{$day} {$hour}:{$minute}:00");
|
||||
}
|
||||
}
|
||||
|
||||
if ($createMomentTime == 0) {
|
||||
$createMomentTime = time();
|
||||
}
|
||||
|
||||
// 判断内容类型
|
||||
$contentType = 4; // 默认文本
|
||||
if (!empty($imageUrls)) {
|
||||
$contentType = 1; // 图文
|
||||
}
|
||||
|
||||
// 创建内容项
|
||||
$item = new ContentItem();
|
||||
$item->libraryId = $libraryId;
|
||||
$item->type = 'diy'; // 自定义类型
|
||||
$item->title = !empty($date) ? $date . ' ' . $placementTime : '导入的内容';
|
||||
$item->content = $content;
|
||||
$item->comment = $selfReply; // 自回评内容
|
||||
$item->contentType = $contentType;
|
||||
$item->resUrls = json_encode($imageUrls, JSON_UNESCAPED_UNICODE);
|
||||
$item->urls = json_encode([], JSON_UNESCAPED_UNICODE);
|
||||
$item->createMomentTime = $createMomentTime;
|
||||
$item->createTime = time();
|
||||
|
||||
// 设置封面图片
|
||||
if (!empty($imageUrls[0])) {
|
||||
$item->coverImage = $imageUrls[0];
|
||||
}
|
||||
|
||||
// 保存其他信息到contentData
|
||||
$contentData = [
|
||||
'date' => $date,
|
||||
'placementTime' => $placementTime,
|
||||
'functionCategory' => $functionCategory,
|
||||
'displayForm' => $displayForm,
|
||||
'selfReply' => $selfReply
|
||||
];
|
||||
$item->contentData = json_encode($contentData, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$item->save();
|
||||
$successCount++;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$failCount++;
|
||||
$errors[] = "第{$rowNum}行处理失败:" . $e->getMessage();
|
||||
\think\facade\Log::error('导入Excel第' . $rowNum . '行失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
|
||||
// 清理临时图片文件
|
||||
foreach ($images as $imagePath) {
|
||||
if (file_exists($imagePath) && strpos($imagePath, sys_get_temp_dir()) === 0) {
|
||||
@unlink($imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理临时Excel文件
|
||||
if (file_exists($tmpFile) && strpos($tmpFile, sys_get_temp_dir()) === 0) {
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '导入完成',
|
||||
'data' => [
|
||||
'success' => $successCount,
|
||||
'fail' => $failCount,
|
||||
'errors' => $errors
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
|
||||
// 清理临时文件
|
||||
foreach ($images as $imagePath) {
|
||||
if (file_exists($imagePath) && strpos($imagePath, sys_get_temp_dir()) === 0) {
|
||||
@unlink($imagePath);
|
||||
}
|
||||
}
|
||||
if (file_exists($tmpFile) && strpos($tmpFile, sys_get_temp_dir()) === 0) {
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
|
||||
return json(['code' => 500, 'msg' => '导入失败:' . $e->getMessage()]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return json(['code' => 500, 'msg' => '导入失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据列序号生成Excel列字母
|
||||
* @param int $index 列索引(从0开始)
|
||||
* @return string 列字母(如A, B, C, ..., Z, AA, AB等)
|
||||
*/
|
||||
private function columnLetter($index)
|
||||
{
|
||||
$letters = '';
|
||||
do {
|
||||
$letters = chr($index % 26 + 65) . $letters;
|
||||
$index = intval($index / 26) - 1;
|
||||
} while ($index >= 0);
|
||||
return $letters;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user