Apache POI組件操作Excel(導入和導出)

 

Apache的POI組件是Java操作Microsoft Office辦公套件的強大API,其中對Word,Excel和PowperPoint都有支持,當然使用較多的還是Excel,因爲Word和PowerPoint用程序動態操作的應用較少。那麼本文就結合POI來介紹一下操作Excel的方法。

這裏介紹兩種方法實現excel的操作。代碼都有註釋,可以很清楚的看懂,一種是循環遍歷excel表格,這個要自己定位一個excel的起點。另外一種是通過java反射機制實現的,根據表頭來實現映射。具體代碼如下:

第一種:

 

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;



import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.util.Region;


public class ExcelUtils {
	
	/**
	 * 讀取EXCEL
	 * @param firstrow 從第幾行開始讀取
	 * @return 讀取後返回數組
	 */
	@SuppressWarnings("deprecation")
	public static String[][] importExcel(File file, int firstrow)
			throws FileNotFoundException, IOException {
		List<String[]> result = new ArrayList<String[]>();
		int rowSize = 0;
		BufferedInputStream in = new BufferedInputStream(new FileInputStream(
				file));
		POIFSFileSystem fs = new POIFSFileSystem(in);
		HSSFWorkbook wb = new HSSFWorkbook(fs);
		HSSFCell cell = null;
		for (int sheetIndex = 0; sheetIndex < wb.getNumberOfSheets(); sheetIndex++) {
			HSSFSheet st = wb.getSheetAt(sheetIndex);
			for (int rowIndex = firstrow; rowIndex <= st.getLastRowNum(); rowIndex++) {
				HSSFRow row = st.getRow(rowIndex);
				if (row == null) {
					continue;
				}
				int tempRowSize = row.getLastCellNum() + 1;
				if (tempRowSize > rowSize) {
					rowSize = tempRowSize;
				}
				String[] values = new String[rowSize];
				Arrays.fill(values, "");
				boolean hasValue = false;

				for (short columnIndex = 0; columnIndex <= row.getLastCellNum(); columnIndex++) {
					String value = "";
					cell = row.getCell(columnIndex);
					if (cell != null) {
						switch (cell.getCellType()) {
						case HSSFCell.CELL_TYPE_STRING://讀取的格式爲字符串
							value = cell.getStringCellValue();
							break;
						case HSSFCell.CELL_TYPE_NUMERIC://讀取的格式爲數組
							//如果格式爲日期格式,自定義格式輸出
							if (HSSFDateUtil.isCellDateFormatted(cell)) {
								Date date = cell.getDateCellValue();
								if (date != null) {
									value = new SimpleDateFormat("yyyy-MM-dd")
											.format(date);
								} else {
									value = "";
								}
							} else {
								//如果格式爲數值,自定義格式輸出
								value = new DecimalFormat().format(cell
										.getNumericCellValue());
							}
							break;
						case HSSFCell.CELL_TYPE_FORMULA:
							// 導入時如果爲公式生成的數據則無值
							value = "";
							break;
							// 導入時如果爲空
						case HSSFCell.CELL_TYPE_BLANK:
							break;
						case HSSFCell.CELL_TYPE_ERROR:
							value = "";
							break;
							// 導入時如果爲BOOLEAN型 自定義格式輸出
						case HSSFCell.CELL_TYPE_BOOLEAN:
							value = (cell.getBooleanCellValue() == true ? "Y"
									: "N");
							break;
						default:
							value = "";
						}
					}

					values[columnIndex] = rightTrim(value);
					hasValue = true;
				}

				if (hasValue) {
					result.add(values);
				}
			}

			in.close();
			String[][] returnArray = new String[result.size()][rowSize];
			for (int i = 0; i < returnArray.length; i++) {
				returnArray[i] = (String[]) result.get(i);
			}
			return returnArray;
		}
		return null;

	}

	/**
	 * 去掉字符串右邊的空格
	 * 
	 * @param str 要處理的字符串
	 * @return 處理後的字符串
	 */
	public static String rightTrim(String str) {
		if (str == null) {
			return "";
		}
		int length = str.length();
		for (int i = length - 1; i >= 0; i--) {
			if (str.charAt(i) != 0x20) {
				break;
			}
			length--;
		}
		return str.substring(0, length);
	}
	
/**
	 * 創建通用EXCEL頭部
	 * 
	 * @param headString 頭部顯示的字符
	 * @param colSum 該報表的列數
	 */
	@SuppressWarnings("deprecation")
	public void createNormalHead(String headString, int colSum,HSSFSheet sheet,HSSFWorkbook wb) {
	    
		HSSFRow row1 = sheet.createRow(0);

		// 設置第一行
		HSSFCell cell = row1.createCell(0);
		row1.setHeight((short) 800);

		// 定義單元格爲字符串類型
		cell.setCellType(HSSFCell.ENCODING_UTF_16);
		cell.setCellValue(new HSSFRichTextString(headString));

		// 指定合併區域
		sheet.addMergedRegion(new Region(0, (short) 0, 0, (short) colSum));

		HSSFCellStyle cellStyle = wb.createCellStyle();

		cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER); // 指定單元格居中對齊
		cellStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);// 指定單元格垂直居中對齊
		//cellStyle.setWrapText(true);// 指定單元格自動換行

		// 設置單元格字體
		HSSFFont font = wb.createFont();
		font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
		font.setFontName("宋體");
		font.setFontHeight((short) 300);
		cellStyle.setFont(font);

		cell.setCellStyle(cellStyle);
	}
	
	/**
	 * 創建通用報表第二行的格式
	 * 
	 */
	public HSSFCellStyle getCellStyle(HSSFWorkbook wb) {
		// 創建單元格樣式
 		HSSFCellStyle cellStyle = wb.createCellStyle();
 		// 指定單元格居中對齊
 		cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);
 		// 指定單元格垂直居中對齊
 		cellStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
 		// 指定當單元格內容顯示不下時自動換行
 		//cellStyle.setWrapText(true);
 		// 設置單元格字體
 		HSSFFont font = wb.createFont();
 		font.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);
 		font.setFontName("宋體");
 		font.setFontHeight((short) 200);
 		//font.setFontHeightInPoints((short) 22);
 		cellStyle.setFont(font);
 		return cellStyle;

	}
	
}

第二種:

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellRangeAddress;

public class ExcelUtil<T> {
	/**
	 * 從excel導入,返回一個list集合
	 * 
	 * @author 
	 * @param file
	 *            導入的excel文件
	 * @param pattern
	 * @return
	 * */
	Class<T> clazz;

	public ExcelUtil(Class<T> clazz) {
		this.clazz = clazz;
	}

	@SuppressWarnings("unchecked")
	public Collection<T> importExcel(File file, String... pattern)
			throws Exception {
		Collection<T> dist = new ArrayList<T>();
		try {
			/**
			 * 類反射得到調用方法
			 */
			// 得到目標目標類的所有的字段列表
			Field filed[] = clazz.getDeclaredFields();
			// 將所有標有Annotation的字段,也就是允許導入數據的字段,放入到一個map中
			Map fieldmap = new HashMap();
			// 循環讀取所有字段
			for (int i = 0; i < filed.length; i++) {
				Field f = filed[i];
				// 得到單個字段上的Annotation
				ExcelAnnotation exa = f.getAnnotation(ExcelAnnotation.class);
				// 如果標識了Annotationd的話
				if (exa != null) {
					// 構造設置了Annotation的字段的Setter方法
					String fieldname = f.getName();
					String setMethodName = "set"
							+ fieldname.substring(0, 1).toUpperCase()
							+ fieldname.substring(1);
					// 構造調用的method,
					Method setMethod = clazz.getMethod(setMethodName,
							new Class[] { f.getType() });
					// 將這個method以Annotaion的名字爲key來存入。
					fieldmap.put(exa.exportName(), setMethod);
				}
			}
			/**
			 * excel的解析開始
			 */
			// 將傳入的File構造爲FileInputStream;
			FileInputStream in = new FileInputStream(file);
			// // 得到工作表
			HSSFWorkbook book = new HSSFWorkbook(in);
			// // 得到第一頁
			HSSFSheet sheet = book.getSheetAt(0);
			// // 得到第一面的所有行
			Iterator<Row> row = sheet.rowIterator();

			/**
			 * 標題解析
			 */
			// 得到第一行,也就是標題行
			Row title = row.next();
			// 得到第一行的所有列
			Iterator<Cell> cellTitle = title.cellIterator();
			// 將標題的文字內容放入到一個map中。
			Map titlemap = new HashMap();
			// 從標題第一列開始
			int i = 0;
			// 循環標題所有的列
			while (cellTitle.hasNext()) {
				Cell cell = cellTitle.next();
				String value = cell.getStringCellValue();
				// 還是把表頭trim一下
				value = value.trim();
				titlemap.put(i, value);
				i = i + 1;
			}
			/**
			 * 解析內容行
			 */
			// 用來格式化日期的DateFormat
			SimpleDateFormat sf;
			if (pattern.length < 1) {
				sf = new SimpleDateFormat("yyyy-MM-dd");
			} else
				sf = new SimpleDateFormat(pattern[0]);
			while (row.hasNext()) {
				// 標題下的第一行
				Row rown = row.next();

				// 行的所有列
				Iterator<Cell> cellbody = rown.cellIterator();
				// 得到傳入類的實例
				T tObject = clazz.newInstance();
				int k = 0;
				// 遍歷一行的列
				while (cellbody.hasNext()) {
					Cell cell = cellbody.next();
					// 這裏得到此列的對應的標題
					String titleString = (String) titlemap.get(k);
					// 如果這一列的標題和類中的某一列的Annotation相同,那麼則調用此類的的set方法,進行設值
					if (fieldmap.containsKey(titleString)) {
						Method setMethod = (Method) fieldmap.get(titleString);
						// 得到setter方法的參數
						Type[] ts = setMethod.getGenericParameterTypes();
						// 只要一個參數
						String xclass = ts[0].toString();
						// 判斷參數類型
						try {
							switch (cell.getCellType()) {
							case HSSFCell.CELL_TYPE_NUMERIC: // 數字
								if (xclass.equals("class java.lang.String")) {
									if ((cell.getNumericCellValue() + "")
											.indexOf(".") > 0) {
										setMethod
												.invoke(tObject,
														(cell.getNumericCellValue() + "")
																.substring(
																		0,
																		(cell.getNumericCellValue() + "")
																				.lastIndexOf(".")));
									}
								} else if (xclass
										.equals("class java.lang.Integer")) {
									setMethod.invoke(tObject,
											(int) cell.getNumericCellValue());
								} else if (xclass.equals("int")) {
									setMethod.invoke(tObject,
											(int) cell.getNumericCellValue());
								}
								break;
							case HSSFCell.CELL_TYPE_STRING: // 字符串
								if (xclass.equals("class java.lang.Integer")) {
									setMethod.invoke(tObject,
											Integer.parseInt(cell
													.getStringCellValue()));
								} else if (xclass
										.equals("class java.lang.String")) {
									setMethod.invoke(tObject, cell
											.getStringCellValue().trim());
								} else if (xclass.equals("int")) {
									int temp = Integer.parseInt(cell
											.getStringCellValue());
									setMethod.invoke(tObject, temp);
								}
								break;
							case HSSFCell.CELL_TYPE_BOOLEAN: // Boolean
								Boolean boolname = true;
								if (cell.getStringCellValue().equals("否")) {
									boolname = false;
								}
								setMethod.invoke(tObject, boolname);
								break;
							case HSSFCell.CELL_TYPE_FORMULA: // 公式
								System.out.print(cell.getCellFormula() + "   ");
								break;
							case HSSFCell.CELL_TYPE_BLANK: // 空值
								System.out.println(" ");
								break;
							case HSSFCell.CELL_TYPE_ERROR: // 故障
								System.out.println(" ");
								break;
							default:
								System.out.print("未知類型   ");
								break;
							}
						} catch (Exception e) {// 轉換出錯
							e.printStackTrace();

						}
					}
					// 下一列
					k = k + 1;
				}
				dist.add(tObject);
			}
		} catch (Exception e) {
			e.printStackTrace();
			// 將異常拋出去
			throw e;
		}
		return dist;
	}

	// 格式化日期
	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

	/**
	 * 導出到excel中去,
	 * 
	 * @author Administrator
	 * @param title
	 *            excel的工作表名
	 * @param fileName
	 *            excel 文件名
	 * @param dateset
	 *            導出的數據集合
	 * @param out
	 *            輸出流
	 * @throws IOException
	 */
	@SuppressWarnings({ "static-access" })
	public void exportExcel(String title, String fileName, List<T> dataset, String totalMoney,
			HttpServletResponse response) throws IOException {
		OutputStream out = response.getOutputStream();// 取得輸出流
		response.reset();// 清空輸出流
		response.setContentType("application/ms-excel;charset=GB2312");
		response.setHeader("Content-disposition", "attachment; filename="
				+ new String(fileName.getBytes("gb2312"), "iso8859-1") + ".xls");
		// 聲明一個工作薄
		try {
			HSSFWorkbook workbook = new HSSFWorkbook();
			// 首先檢查數據看是否是正確的
			if (dataset == null || dataset.size() == 0 || title == null
					|| out == null) {
				throw new Exception("傳入的數據不對!");
			}
			// 取得實際泛型類
			T ts = (T) dataset.get(0);
			Class<?> tCls = ts.getClass();
			// 生成一個表格
			HSSFSheet sheet = workbook.createSheet(title);
			// 設置表格默認列寬度爲15個字節
			sheet.setDefaultColumnWidth(15);
			// 生成一個樣式
			HSSFCellStyle style = workbook.createCellStyle();
			// 設置標題樣式
			style = this.setHeadStyle(workbook, style);

			// 得到所有字段

			Field filed[] = ts.getClass().getDeclaredFields();
			// 標題
			List<String> exportfieldtile = new ArrayList<String>();
			// 導出的字段的get方法
			List<Method> methodObj = new ArrayList<Method>();
			// 遍歷整個filed
			for (int i = 0; i < filed.length; i++) {
				Field f = filed[i];
				ExcelAnnotation exa = f.getAnnotation(ExcelAnnotation.class);
				// 如果設置了annottion
				if (exa != null) {
					String exprot = exa.exportName();
					// 添加到標題
					exportfieldtile.add(exprot);
					// 添加到需要導出的字段的方法
					String fieldname = f.getName();
					String getMethodName = "get"
							+ fieldname.substring(0, 1).toUpperCase()
							+ fieldname.substring(1);

					Method getMethod = tCls.getMethod(getMethodName,
							new Class[] {});

					methodObj.add(getMethod);
				}
			}
			sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, exportfieldtile
					.size() - 1));
			HSSFRow titleRow = sheet.createRow(0);
			HSSFCell titleCell = titleRow.createCell(0);
			titleCell.setCellValue(title);
			titleCell.setCellStyle(this.setTitleStyle(workbook,
					workbook.createCellStyle()));
			// 產生表格標題行
			HSSFRow row = sheet.createRow(1);
			for (int i = 0; i < exportfieldtile.size(); i++) {
				HSSFCell cell = row.createCell(i);
				cell.setCellStyle(style);
				HSSFRichTextString text = new HSSFRichTextString(
						exportfieldtile.get(i));
				cell.setCellValue(text);
			}

			int index = 1;

			// 循環整個list
			for (int j = 0; j < dataset.size(); j++) {
				// 從第二行開始寫,第一行是標題
				T t = (T) dataset.get(j);
				row = sheet.createRow(index + 1);
				for (int k = 0; k < methodObj.size(); k++) {
					HSSFCell cell = row.createCell(k);
					Method getMethod = methodObj.get(k);
					Object value = getMethod.invoke(t, new Object[] {});
					String textValue = getValue(value);
					cell.setCellValue(textValue);
				}
				index++;
			}
			
			//總的項目筆數
			HSSFRow totalNumRow = sheet.createRow(index + 2);
			HSSFCell totalNumCellTitle = totalNumRow.createCell(0);
			totalNumCellTitle.setCellStyle(setStatStyle(workbook, workbook.createCellStyle()));
			totalNumCellTitle.setCellValue("項目筆數");
			HSSFCell totalNumCell = totalNumRow.createCell(1);
			totalNumCell.setCellStyle(setStatStyle(workbook, workbook.createCellStyle()));
			totalNumCell.setCellValue(dataset.size());
			
			//總金額
			HSSFRow totalMoneyRow = sheet.createRow(index + 3);
			HSSFCell totalMoneyCellTitle = totalMoneyRow.createCell(0);
			totalMoneyCellTitle.setCellStyle(setStatStyle(workbook, workbook.createCellStyle()));
			totalMoneyCellTitle.setCellValue("總金額");
			HSSFCell totalMoneyCell = totalMoneyRow.createCell(1);
			totalMoneyCell.setCellStyle(setStatStyle(workbook, workbook.createCellStyle()));
			totalMoneyCell.setCellValue(totalMoney + "萬元");
			workbook.write(out);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public String getValue(Object value) {
		String textValue = "";
		if (value == null)
			return textValue;

		if (value instanceof Boolean) {
			boolean bValue = (Boolean) value;
			textValue = "是";
			if (!bValue) {
				textValue = "否";
			}
		} else if (value instanceof Date) {
			Date date = (Date) value;

			textValue = sdf.format(date);
		} else
			textValue = value.toString();

		return textValue;
	}

	/**
	 * 初始化導出的excel標題的樣式
	 * */
	public static HSSFCellStyle setTitleStyle(HSSFWorkbook workbook,
			HSSFCellStyle style) {

		style.setFillForegroundColor(HSSFColor.WHITE.index);  
        style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);  
        style.setBorderBottom(HSSFCellStyle.BORDER_THIN);  
        style.setBorderLeft(HSSFCellStyle.BORDER_THIN);  
        style.setBorderRight(HSSFCellStyle.BORDER_THIN);  
        style.setBorderTop(HSSFCellStyle.BORDER_THIN);  
        style.setAlignment(HSSFCellStyle.ALIGN_CENTER);  
        // 生成字體  
        HSSFFont font = workbook.createFont();  
        font.setColor(HSSFColor.BLACK.index);  
        font.setFontHeightInPoints((short) 16);  
        font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);  
        // 把字體應用到當前的樣樣式  
        style.setFont(font);  
        return style;

	}
	
	/**
	 * 統計部分的樣式
	 * @param workbook
	 * @param style
	 * @return
	 */
	public static HSSFCellStyle setStatStyle(HSSFWorkbook workbook,
			HSSFCellStyle style) {
		
		style.setFillForegroundColor(HSSFColor.WHITE.index);
		style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
		style.setBorderBottom(HSSFCellStyle.BORDER_NONE);
		style.setBorderLeft(HSSFCellStyle.BORDER_NONE);
		style.setBorderRight(HSSFCellStyle.BORDER_NONE);
		style.setBorderTop(HSSFCellStyle.BORDER_NONE);
		style.setAlignment(HSSFCellStyle.ALIGN_LEFT);
		// 生成字體
		HSSFFont font = workbook.createFont();
		font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
		// 把字體應用到當前的樣樣式
		style.setFont(font);
		return style;
		
	}

	/**
	 * 初始化導出的excel樣式
	 * */
	public static HSSFCellStyle setHeadStyle(HSSFWorkbook workbook,
			HSSFCellStyle style) {

		style.setFillForegroundColor(HSSFColor.SKY_BLUE.index);  
        style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);  
        style.setBorderBottom(HSSFCellStyle.BORDER_THIN);  
        style.setBorderLeft(HSSFCellStyle.BORDER_THIN);  
        style.setBorderRight(HSSFCellStyle.BORDER_THIN);  
        style.setBorderTop(HSSFCellStyle.BORDER_THIN);  
        style.setAlignment(HSSFCellStyle.ALIGN_CENTER);  
        // 生成字體  
        HSSFFont font = workbook.createFont();  
        font.setColor(HSSFColor.VIOLET.index);  
        font.setFontHeightInPoints((short) 12);  
        font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);  
        // 把字體應用到當前的樣樣式  
        style.setFont(font);  
        return style;  

	}

	public static HSSFCellStyle setbodyStyle(HSSFWorkbook workbook,
			HSSFCellStyle style2) {
		style2.setFillForegroundColor(HSSFColor.LIGHT_YELLOW.index);  
        style2.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);  
        style2.setBorderBottom(HSSFCellStyle.BORDER_THIN);  
        style2.setBorderLeft(HSSFCellStyle.BORDER_THIN);  
        style2.setBorderRight(HSSFCellStyle.BORDER_THIN);  
        style2.setBorderTop(HSSFCellStyle.BORDER_THIN);  
        style2.setAlignment(HSSFCellStyle.ALIGN_CENTER);  
        style2.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);  
        // 生成字體  
        HSSFFont font2 = workbook.createFont();  
        font2.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);  
        // 把字體應用到當前的樣樣式  
        style2.setFont(font2);  
        return style2;
	}
}


 

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 用於Excel導出時給每個pojo對象的字段添加字段名稱,作爲excel的表頭
 * 
 * */
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.FIELD)  
public @interface ExcelAnnotation {  
    // excel導出時標題顯示的名字,如果沒有設置Annotation屬性,將不會被導出和導入  
    public String exportName();  
}



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章